實(shí)踐requestAnimationFrame平滑動畫

本文源自我的公眾號: 實(shí)踐requestAnimationFrame平滑動畫

背景

前端領(lǐng)域?qū)崿F(xiàn)動畫效果通常有這么幾種方式: css animation悍赢、setTimeout(setInterval)Lottie玄妈。

  • css animation 動畫是通過關(guān)鍵幀(@keyframes) 來實(shí)現(xiàn)的,優(yōu)點(diǎn)是寫法比較簡單笨触,缺點(diǎn)就是難以獲取動畫的開始和結(jié)束事件丛版。

  • setTimeout 一般通過callback 來控制元素的變化實(shí)現(xiàn)動畫的穿剖,但是定時器動畫一直存在兩個問題超埋,第一個就是動畫的循時間環(huán)間隔不好確定哲思,設(shè)置長了動畫顯得不夠平滑流暢洼畅,設(shè)置短了瀏覽器的重繪頻率會達(dá)到瓶頸,推薦的最佳循環(huán)間隔是17ms(大多數(shù)電腦的顯示器刷新頻率是60Hz棚赔,1000ms/60)帝簇;第二個問題是定時器第二個時間參數(shù)只是指定了多久后將動畫任務(wù)添加到瀏覽器的UI線程隊(duì)列中徘郭,如果UI線程處于忙碌狀態(tài),那么動畫不會立刻執(zhí)行丧肴。

  • lottie 一般是使用 canvas 或者 svg 方式來實(shí)現(xiàn)動畫的残揉, 通過引入配置文件來實(shí)現(xiàn)。但是復(fù)雜動畫會導(dǎo)致配置文件過大芋浮,進(jìn)而導(dǎo)致webpack打包體積過于龐大抱环。

本次要實(shí)現(xiàn)能量球的 軌跡運(yùn)動 動畫 及 總能量縮放 動畫,以上的方案實(shí)踐下來均不能實(shí)現(xiàn)平滑的效果纸巷。后來了解到 H5 中加入了 requestAnimationFrame镇草,該方法是根據(jù)瀏覽器的刷新頻率來執(zhí)行回調(diào)方法,可以很好的控制動畫的開始和結(jié)束事件何暇。

尷尬陶夜,簡書上傳不了視頻凛驮,如果看效果裆站,請移步到公眾號文章地址查看。 https://mp.weixin.qq.com/s/yTex4ewF0cbG1tluaqxKzw

說明

window.requestAnimationFrame() 告訴瀏覽器——你希望執(zhí)行一個動畫黔夭,并且要求瀏覽器在下次重繪之前調(diào)用指定的回調(diào)函數(shù)更新動畫宏胯。該方法需要傳入一個回調(diào)函數(shù)作為參數(shù),該回調(diào)函數(shù)會在瀏覽器下一次重繪之前執(zhí)行本姥。

注意:若你想在瀏覽器下次重繪之前繼續(xù)更新下一幀動畫肩袍,那么回調(diào)函數(shù)自身必須再次調(diào)用window.requestAnimationFrame()

當(dāng)你準(zhǔn)備更新動畫時你應(yīng)該調(diào)用此方法。這將使瀏覽器在下一次重繪之前調(diào)用你傳入給該方法的動畫函數(shù)(即你的回調(diào)函數(shù))婚惫》沾停回調(diào)函數(shù)執(zhí)行次數(shù)通常是每秒60次,但在大多數(shù)遵循W3C建議的瀏覽器中先舷,回調(diào)函數(shù)執(zhí)行次數(shù)通常與瀏覽器屏幕刷新次數(shù)相匹配艰管。為了提高性能和電池壽命,因此在大多數(shù)瀏覽器里蒋川,當(dāng)requestAnimationFrame() 運(yùn)行在后臺標(biāo)簽頁或者隱藏的iframe 里時牲芋,requestAnimationFrame() 會被暫停調(diào)用以提升性能和電池壽命。

回調(diào)函數(shù)會被傳入DOMHighResTimeStamp參數(shù)捺球,DOMHighResTimeStamp指示當(dāng)前被 requestAnimationFrame() 排序的回調(diào)函數(shù)被觸發(fā)的時間缸浦。在同一個幀中的多個回調(diào)函數(shù),它們每一個都會接受到一個相同的時間戳氮兵,即使在計(jì)算上一個回調(diào)函數(shù)的工作負(fù)載期間已經(jīng)消耗了一些時間裂逐。該時間戳是一個十進(jìn)制數(shù),單位毫秒泣栈,最小精度為1ms(1000μs)絮姆。

請確弊碓總是使用第一個參數(shù)(或其它獲得當(dāng)前時間的方法)計(jì)算每次調(diào)用之間的時間間隔,否則動畫在高刷新率的屏幕中會運(yùn)行得更快篙悯。

語法

window.requestAnimationFrame(callback);

參數(shù)

callback

下一次重繪之前更新動畫幀所調(diào)用的函數(shù)(即上面所說的回調(diào)函數(shù))蚁阳。該回調(diào)函數(shù)會被傳入DOMHighResTimeStamp參數(shù),該參數(shù)與performance.now()的返回值相同鸽照,它表示requestAnimationFrame() 開始去執(zhí)行回調(diào)函數(shù)的時刻螺捐。

返回值

一個 long 整數(shù),請求 ID 矮燎,是回調(diào)列表中唯一的標(biāo)識定血。是個非零值,沒別的意義诞外。你可以傳這個值給 window.cancelAnimationFrame() 以取消回調(diào)函數(shù)澜沟。

范例

以下代碼根據(jù)回調(diào)的時間戳傳參(說明:這里是timestamp 表示為從time origin之后到當(dāng)前調(diào)用時經(jīng)過的時間),確保不同刷新頻率的屏幕 都可以在 兩秒內(nèi)停止動畫峡谊。

const element = document.getElementById('some-element-you-want-to-animate');
let start;

function step(timestamp) {
 if (start === undefined)
   start = timestamp;
 const elapsed = timestamp - start;

 //這里使用`Math.min()`確保元素剛好停在200px的位置茫虽。
 element.style.transform = 'translateX(' + Math.min(0.1 * elapsed, 200) + 'px)';

 if (elapsed < 2000) { // 在兩秒后停止動畫
   window.requestAnimationFrame(step);
 }
}

window.requestAnimationFrame(step);

實(shí)踐

本次實(shí)踐收能量平滑動畫不考慮高刷新頻率的屏幕,暫定為用戶使用的都是 一秒鐘 60 刷新頻次的屏幕既们。動畫分三部分:向下動畫濒析、軌跡動畫、縮放動畫啥纸。

  • 下滑動畫

收取能量球開始有個向下的動畫号杏,每次下滑2px,能量球下滑到 60 px 的時候(下滑動畫執(zhí)行了30次斯棒,按照一秒鐘刷新60次盾致,動畫大約執(zhí)行0.5秒),則執(zhí)行向右上角的軌跡動畫荣暮,用 requestAnimationFrame 可以這么實(shí)現(xiàn):

// 點(diǎn)擊能量球事件
 clickBall() {
   this.dropY = 0;
   this.perY = 2;
   requestAnimationFrame(this.dropBall.bind(this));
 }
 // 下滑軌跡動畫
 dropBall() {
   if(this.dropY < 60) {
     this.dropY += this.perY;
     this.ballRef.current.style.top = `${this.props.style.top + this.dropY}px`;
     requestAnimationFrame(this.dropBall.bind(this));
   } else {
     // 軌跡動畫
     this.parabola();
   }
 }
  • 軌跡動畫

軌跡動畫庭惜,則通過 getBoundingClientRect 方法 分別獲取 開始元素(能量球當(dāng)前位置)坐標(biāo) 和 結(jié)束元素(總能量位置)坐標(biāo)的位置, 進(jìn)而計(jì)算出需要運(yùn)動的距離渠驼。根據(jù)需要的時間和每秒60次的刷新頻率蜈块,計(jì)算出 每次 translate 的距離即可。(可以參照張鑫旭實(shí)踐的軌跡動畫迷扇,此處不列舉實(shí)現(xiàn)方式)百揭。

  • 縮放動畫

最后是總能量的縮放動畫(這里實(shí)現(xiàn)的縮放是 從1.0 放大到1.3, 然后再縮回到1.0):

// 總能量收取事件 及 觸發(fā)動畫效果
 collectPower({energyNum}) {
   this.totalPower += energyNum;
   this.powerRef.current.innerText = this.totalPower;
   this.minuteFlag = false;
   this.scale = 1.0;
   this.perAdd = 0.02;
   requestAnimationFrame(this.scalePower.bind(this));
 }

 // 動畫效果
 scalePower() {
   if (this.scale < 1.3 && !this.minuteFlag) {
     this.scale +=  this.perAdd;
     this.powerRef.current.style.transform = `scale(${this.scale})`;
     requestAnimationFrame(this.scalePower.bind(this));
   } else if (this.scale > 1) {
     this.minuteFlag = true;
     this.scale -= this.perAdd;
     this.powerRef.current.style.transform = `scale(${this.scale})`;
     requestAnimationFrame(this.scalePower.bind(this));
   } else {
     this.powerRef.current.style.transform = `scale(1)`;
   }
 }

參考資料:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蜓席,一起剝皮案震驚了整個濱河市器一,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌厨内,老刑警劉巖祈秕,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渺贤,死亡現(xiàn)場離奇詭異,居然都是意外死亡请毛,警方通過查閱死者的電腦和手機(jī)志鞍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來方仿,“玉大人固棚,你說我怎么就攤上這事∠裳粒” “怎么了此洲?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長委粉。 經(jīng)常有香客問我呜师,道長,這世上最難降的妖魔是什么贾节? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任汁汗,我火速辦了婚禮,結(jié)果婚禮上氮双,老公的妹妹穿的比我還像新娘碰酝。我一直安慰自己霎匈,他們只是感情好戴差,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著铛嘱,像睡著了一般暖释。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上墨吓,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天球匕,我揣著相機(jī)與錄音,去河邊找鬼帖烘。 笑死亮曹,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的秘症。 我是一名探鬼主播照卦,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼乡摹!你這毒婦竟也來了役耕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤聪廉,失蹤者是張志新(化名)和其女友劉穎瞬痘,沒想到半個月后故慈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡框全,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年察绷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片津辩。...
    茶點(diǎn)故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡克婶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出丹泉,到底是詐尸還是另有隱情情萤,我是刑警寧澤,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布摹恨,位于F島的核電站筋岛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏晒哄。R本人自食惡果不足惜睁宰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望寝凌。 院中可真熱鬧柒傻,春花似錦、人聲如沸较木。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伐债。三九已至预侯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間峰锁,已是汗流浹背萎馅。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留虹蒋,地道東北人糜芳。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像魄衅,于是被迫代替她去往敵國和親峭竣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評論 2 361