一、初識requestAnimationFrame
requestAnimationFrame解決了浏覽器不知道javascript動畫什麼時候開始、不知道最佳循環間隔時間的問題。它是跟著浏覽器的繪制走的,如果浏覽器繪制間隔是16.7ms,它就按這個間隔繪制;如果浏覽器繪制間隔是10ms, 它就按10ms繪制。這樣就不會存在過度繪制的問題,動畫不會丟幀。
內部是這麼運作的:
浏覽器頁面每次要重繪,就會通知requestAnimationFrame;
這是資源非常高效的一種利用方式。
怎麼講呢?
有以下兩點:
1、就算很多個requestAnimationFrame()
要執行,浏覽器只要通知一次就可以了。而setTimeout是多個獨立繪制。
2、一旦頁面不出於當前頁面(比如:頁面最小化了),頁面是不會進行重繪的,自然requestAnimationFrame也不會觸發(因為沒有通知)。頁面繪制全部停止,資源高效利用。
編
二. 動畫的循環間隔
編寫動畫循環的關鍵,是要知道延遲時間多長合適。一方面,循環時間必須足夠短,這樣才能保證動畫效果更平滑流暢;另一方面,循環還要足夠長,這樣才能保證浏覽器有能力渲染產生的變化。大多數顯示器的刷新頻率是60Hz,相當於每秒鐘重繪60次。大多數浏覽器都會對重繪操作加以限制,不超過顯示器的重繪頻率,因為即使超過了這個頻率,用戶體驗也不會有提升。
因此最平滑動畫的最佳循環間隔是1000ms/60,約等於17ms。以這個循環間隔重繪的動畫是平滑的,因為這個速度最接近浏覽器的最高限速。為了適應17ms的循環間隔,多重動畫可能需要加以節制,以便不會完成得太快。
雖然與使用多組setTimeout()
相比,使用setInterval()
的動畫循環效率更高。但是無論setTimeout()
還是setInterval()
都不十分精確。為它們傳入的第二個參數,實際上只是指定了把動畫代碼添加到浏覽器UI線程隊列以等待執行的時間。如果隊列前面已經加入了其他任務,那動畫代碼就要等前面的任務執行完成後再執行。如果UI線程繁忙,比如忙於處理用戶操作,那麼即使把代碼加入隊列也不會立即執行。
因此,知道什麼時候繪制下一幀是保證動畫平滑的關鍵。然而,面對不十分精確的setTimeout()
和setInterval()
,開發人員至今都沒有辦法確保浏覽器按時繪制下一幀。
以下是幾個浏覽器的計時器精度:
IE8及其以下版本浏覽器: 15.6ms;
IE9及其以上版本浏覽器:4ms;
Firefox和Safari:10ms;
Chrome:4ms。
更為復雜的是,浏覽器開始限制後台標簽頁或不活動標簽頁的計數器。因此,即使你優化了循環間隔,可能仍然只能接近你想要的效果。
三. requestAnimationFrame()
Mozilla的 Robert O'Callahan 指出,CSS變換動畫的優勢在於浏覽器知道動畫什麼時候開始,因此會計算出正確的循環間隔,在適當的時候刷新UI。而對於JavaScript動畫,浏覽器就無從知曉什麼時候開始。
因此Robert O'Callahan的方案是,創建一個新方法mozRequestAnimationFrame()
,通過它告訴浏覽器某些代碼將要執行動畫。這樣浏覽器可以在運行某些代碼後進行適當的優化。
與setTimeout()
和setInterval()
方法不同,requestAnimationFrame()
不需要調用者指定幀速率,浏覽器會自行決定最佳的幀效率。
requestAnimationFrame()
方法接收一個參數,即在重繪屏幕前調用以個函數。這個函數負責改變下一次重繪時的DOM樣式。為了創建動畫循環,可以像使用setTimeout()一樣,把多個對requestAnimationFrame()
的調用連綴起來。
如:
function drawFrame() { window.requestAnimationFrame(drawFrame); // animation code... } window.requestAnimationFrame(drawFrame);
三. requestAnimationFrame()的兼容性
3.1 requestAnimationFrame()的兼容性封裝:
由於mozRequestAnimationFrame()
是HTML5的新功能,目前各大浏覽器的支持情況各異。如果希望代碼具備更好的跨平台性,可以考慮使用下面的代碼實現各平台兼容性:
if(!window.requestAnimationFrame) { window.requestAnimationFrame = (window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { var self = this, start, finish; return window.setTimeout(function() { start = +new Date(); callback(start); finish = +new Date(); self.timeout = 1000/60 - (finish - start); }, self.timeout); }); }
這段代碼先檢查了window.requestAnimationFrame
函數的定義是否存在。如果不存在,就遍歷已知的各種浏覽器實現並替代該函數。如果還是找不到一個與浏覽器相關的實現,它最終會采用基於JavaScript定時器的動畫以每秒60幀的間隔調用setTimeout函數。
mozRequestAnimationFrame()
會接收一個時間碼(從1970年1月1日起至今的毫秒數),表示下一次重繪的實際發生時間。這樣,mozRequestAnimationFrame()
就會根據這個時間碼設定將來的某個時刻進行重繪。
但是webkitRequestAnimationFrame()
和msRequestAnimationFrame()
不會給回調函數傳遞時間碼,因此無法知道下一次重繪將發生在什麼時間。
如果要計算兩次重繪的時間間隔,Firefox中可以使用既有的時間碼,而在Chrome和IE則可以使用不太精確地Date()對象。
3.2 cancelRequestAnimFrame()的兼容性封裝:
W3C也提供了cancelRequestAnimationFrame()
方法,用於取消回調函數。requestAnimationFrame()
方法會返回一個對象,用做標識回掉函數身份。若要取消回調函數的執行,可將其傳給cancelRequestAnimationFrame()
。
window.cancelRequestAnimFrame = ( function() { return window.cancelAnimationFrame || window.webkitCancelRequestAnimationFrame || window.mozCancelRequestAnimationFrame || window.oCancelRequestAnimationFrame || window.msCancelRequestAnimationFrame || clearTimeout; } )();
3.3 requestAnimationFrame()升級版封裝方法:
另外還有一種更優雅的requestAnimationFrame()
的兼容性封裝方法:
(function() { var lastTime = 0; var vendors = ['ms', 'moz', 'webkit', 'o']; for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; } if (!window.requestAnimationFrame) window.requestAnimationFrame = function(callback, element) { var currTime = new Date().getTime(); var timeToCall = Math.max(0, 16 - (currTime - lastTime)); var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); lastTime = currTime + timeToCall; return id; }; if (!window.cancelAnimationFrame) window.cancelAnimationFrame = function(id) { clearTimeout(id); }; }());
總結
以上就是這篇文章的全部內容,希望能對大家的學習或者工作帶來一定的幫助,如果有疑問大家可以留言交流。