在前端做一些持續執行的動畫時,一般會通過setTimeOut去實現,其實js還有另一個API和setTimeout功能類似,就是requestAnimationFrame,在說requestAnimationFrame之前項說說setTimeOut和setInterval
setTimeout 和 setInterval區別
- setTimeout: 指定延期后調用函數,每次
setTimeout
計到期到向事件隊列推入一個事件 - setInterval:以指定周期,向事件隊列推入一個事件
setInterval存在的一些問題:
定時器代碼可能在代碼再次被添加到隊列之前還沒有完成執行,結果導致定時器代碼連續運行好幾次,而之間沒有任何停頓。
而javascript引擎對這個問題的解決是:當使用setInterval()時,僅當沒有該定時器的任何其他代碼實例時,才將定時器代碼添加到隊列中。這確保了定時器代碼加入到隊列中的最小時間間隔為指定間隔。
但是,這樣會導致兩個問題:
- 1、某些間隔被跳過;
- 2、多個定時器的代碼執行之間的間隔可能比預期的小
使用setTimeout構造輪詢模擬定時器
在前一個定時器代碼執行完之前,不會向隊列插入新的定時器代碼,確保不會有任何缺失的間隔。而且,它可以保證在下一次定時器代碼執行之前,至少要等待指定的間隔,避免了連續的運行
setTimeout(function fn(){ console.log('我被調用了'); setTimeout(fn, 100); },100);
requestAnimationFrame
requestAnimationFrame
是瀏覽器用於定時循環操作的一個接口,類似於setTimeout,主要用途是按幀對網頁進行重繪。
顯示器有固定的刷新頻率(60Hz或75Hz),也就是說,每秒最多只能重繪60次或75次,requestAnimationFrame
的基本思想就是與這個刷新頻率保持同步,利用這個刷新頻率進行頁面重繪。
此外,使用這個API,一旦頁面不處於瀏覽器的當前標簽,就會自動停止刷新。這就節省了CPU、GPU和電力。
requestAnimationFrame
是在主線程上完成。這意味着,如果主線程非常繁忙,requestAnimationFrame
的動畫效果會大打折扣。
requestAnimationFrame
使用一個回調函數作為參數。這個回調函數會在瀏覽器重繪之前調用
requestID = window.requestAnimationFrame(callback);
兼容處理
window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function( callback ){ window.setTimeout(callback, 1000 / 60); }; })();
如何使用
function animation(){ requestAnimFrame(animation); }
結束動畫
cancelAnimationFrame(animationId);
requestIdleCallback()
requestIdleCallback會在每次屏幕刷新時,判斷當前幀是否還有多余的時間,如果有,則會調用回調函數
利用這個特性,我們可以在動畫執行的期間,利用每幀的空閑時間來進行數據發送的操作,或者一些優先級比較低的操作,此時不會使影響到動畫的性能,或者和requestAnimationFrame搭配,可以實現一些頁面性能方面的的優化,
react 的
fiber
架構也是基於requestIdleCallback
實現的
總結
- 從單線程模型和任務隊列出發理解
setTimeout(fn, 0)
,並不是立即執行。 - JS 動畫用
requestAnimationFrame
會比setInterval
效果更好 requestIdleCallback()
常用來切割長任務,利用空閑時間執行,避免主線程長時間阻塞。