對(duì)setTimeout和requestAnimationFrame的理解

事件環(huán)

來源 Jake Archibold的事件輪詢演講視頻

事件環(huán).gif

在剛開學(xué)使用javascript制作逐幀動(dòng)畫的時(shí)候使用的是setTimeout和setInterval這兩個(gè)api來繪制動(dòng)畫幀钉赁。由于setInterval添加的事件隊(duì)列會(huì)由于任務(wù)執(zhí)行時(shí)間過長(zhǎng)而導(dǎo)致隊(duì)列添加出現(xiàn)錯(cuò)誤衫生,所以一般我都是用setTimeout來調(diào)用。代碼很簡(jiǎn)單:

function animateTimeout() {
        animateFunc();
        setTimeout(animateTimeout)
}

只需要采用這樣的一種調(diào)用方式赋秀,就能讓你定義的animateFunc動(dòng)作方法能夠?qū)崿F(xiàn)緩動(dòng)播放的效果奴潘。起初我并未發(fā)現(xiàn)這樣有何不妥,直到我使用了requestAnimationFrame這個(gè)api來實(shí)現(xiàn)了同樣的緩動(dòng)動(dòng)畫之后我就發(fā)現(xiàn)了差別竞穷。先看一下下面這段代碼:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      #divContainer {
        width: 300px;
        height: 300px;
        background-color: red;
      }
      #divContainer2 {
        width: 300px;
        height: 300px;
        background-color: yellow;
      }
    </style>
  </head>
  <body>
    <div id="divContainer">timeoutDiv</div>
    <div id="divContainer2">animateDiv</div>
    <script>
      let marginGap = 0;
      let marginGap2 = 0;

      function animateTimeout() {
        divContainer.style.marginLeft = `${++marginGap}px`;
        setTimeout(animateTimeout, 0);
      }
      function animateReq(req) {
        divContainer2.style.marginLeft = `${++marginGap2}px`;
        requestAnimationFrame(animateReq);
      }

      animateTimeout();
      animateReq();
    </script>
  </body>
</html>

這里定義了divContainerdivContainer2兩個(gè)盒子贯吓,分別使用setTimeoutrequestAnimationFrame來定時(shí)改變他們的左邊距,達(dá)到一個(gè)平移的動(dòng)畫效果弓柱。然而我卻發(fā)現(xiàn)使用setTimeout的移動(dòng)速度明顯比requestAnimationFrame快很多

animate.gif

可以在animateTimeout()animateReq()打印日志看看這兩個(gè)方法的執(zhí)行頻率:

function animateTimeout() {
        console.log('logTimeout');
        divContainer.style.marginLeft = `${++marginGap}px`;
        setTimeout(animateTimeout, 0);
      }
      function animateReq(req) {
        console.log('logRequest');
        divContainer2.style.marginLeft = `${++marginGap2}px`;
        requestAnimationFrame(animateReq);
      }
image.png

可以看到animateReq()執(zhí)行一次沟堡,animateTimeout()差不多會(huì)執(zhí)行3次疮鲫。所以會(huì)看到animateTimeout()的速度比animateReq()快了差不多3倍。那為什么會(huì)這樣呢弦叶?于是我先查詢了一下requestAnimationFrame的文檔俊犯,發(fā)現(xiàn)這樣一段說明文字

This will request that your animation function be called before the browser performs the next repaint. The number of callbacks is usually 60 times per second, but will generally match the display refresh rate in most web browsers as per W3C recommendation

依據(jù)文檔所說,瀏覽器會(huì)在下次重繪之前調(diào)用requestAnimationFrame里面的回調(diào)方法伤哺,并且回調(diào)執(zhí)行的頻率是每秒60次燕侠,但是通常會(huì)按照W3C推薦的標(biāo)準(zhǔn)適配顯示器的刷新頻率。也就是說按照大多數(shù)顯示器每秒60次的刷新頻率來確定動(dòng)畫幀的時(shí)間是最合適的立莉,這個(gè)時(shí)間段做出來的動(dòng)畫看起來是最平滑的绢彤。所以上面的例子中的setTimeout在調(diào)用的時(shí)候,延遲時(shí)間的參數(shù)0ms蜓耻,那么一秒鐘就會(huì)添加1000個(gè)動(dòng)畫函數(shù)的任務(wù)茫舶,頁面會(huì)渲染1000次。但是這顯然超過了顯示器的每秒60次刷新頻率刹淌,所以就會(huì)將這1000次的渲染任務(wù)適配到60次饶氏,那么每次渲染就會(huì)執(zhí)行多次任務(wù)。要想使用setTimeout達(dá)到和顯示器刷新頻率相同的渲染那么在設(shè)置任務(wù)時(shí)間間隔的時(shí)候就要使用1000 / 60有勾,大概是16.7ms渲染一幀的動(dòng)畫

function animateTimeout() {
        divContainer.style.marginLeft = `${++marginGap}px`;
        setTimeout(animateTimeout, 1000 / 60);
      }
timeoutAnimate.gif

可以看到這下animateTimeout()animateReq()的移動(dòng)速度就差不多了疹启。但是移動(dòng)一段時(shí)間之后animateTimeout()還是跑到了animateReq()前面。使用setTimeoutsetInterval并不是那么精確蔼卡。因?yàn)樗鼈冊(cè)O(shè)置的時(shí)間間隔是將動(dòng)畫回調(diào)在指定的時(shí)間后添加到任務(wù)隊(duì)列中喊崖,并不代表它會(huì)到時(shí)間就執(zhí)行。此時(shí)如果主線程里面如果還有其他任務(wù)那么就不會(huì)去執(zhí)行它雇逞,如果發(fā)生這種情況就會(huì)阻塞頁面渲染荤懂,因?yàn)殇秩臼且鹊侥_本任務(wù)執(zhí)行完成之后才會(huì)進(jìn)行。而requestAnimateFrame是發(fā)生在渲染的時(shí)候塘砸,它能明確的知道動(dòng)畫什么時(shí)候開始节仿,什么時(shí)候執(zhí)行。

animate.gif

為什么會(huì)是將近3倍谣蠢?

剛開始理解這一塊的時(shí)候一直有疑問粟耻,為什么之前setTimeout延遲參數(shù)為0的時(shí)候只快3倍查近。因?yàn)榘凑彰棵?0次刷新的頻率均攤下來的話眉踱,每次也應(yīng)該要多執(zhí)行1000 / 60大概16.7次才對(duì)。后來查閱一些資料才知道:setTimeout的延遲參數(shù)有一個(gè)默認(rèn)的最小值4.7霜威。當(dāng)不傳延遲參數(shù)谈喳,或者小于4.7的時(shí)候,setTimeout就是使用默認(rèn)的4.7戈泼。所以真正多渲染的次數(shù)應(yīng)該是16.7 / 4.7婿禽,將近3~4倍赏僧。

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市扭倾,隨后出現(xiàn)的幾起案子淀零,更是在濱河造成了極大的恐慌,老刑警劉巖膛壹,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驾中,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡模聋,警方通過查閱死者的電腦和手機(jī)肩民,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來链方,“玉大人持痰,你說我怎么就攤上這事∷钍矗” “怎么了工窍?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)前酿。 經(jīng)常有香客問我移剪,道長(zhǎng),這世上最難降的妖魔是什么薪者? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任纵苛,我火速辦了婚禮,結(jié)果婚禮上言津,老公的妹妹穿的比我還像新娘攻人。我一直安慰自己,他們只是感情好悬槽,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布怀吻。 她就那樣靜靜地躺著,像睡著了一般初婆。 火紅的嫁衣襯著肌膚如雪蓬坡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天磅叛,我揣著相機(jī)與錄音屑咳,去河邊找鬼。 笑死弊琴,一個(gè)胖子當(dāng)著我的面吹牛兆龙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播敲董,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼紫皇,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼慰安!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起聪铺,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤化焕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后铃剔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锣杂,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年番宁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了元莫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蝶押,死狀恐怖踱蠢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情棋电,我是刑警寧澤茎截,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站赶盔,受9級(jí)特大地震影響企锌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜于未,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一撕攒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧烘浦,春花似錦抖坪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至握侧,卻和暖如春蚯瞧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背品擎。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工埋合, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人孽查。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓饥悴,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親盲再。 傳聞我的和親對(duì)象是個(gè)殘疾皇子西设,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348