神奇的requestAnimationFrame

引入

計時器一直是JavaScript動畫的核心技術。而編寫動畫循環的關鍵是要知道延遲時間多長合適。一方面,循環間隔必須足夠短,這樣才能讓不一樣的動畫效果顯得平滑流暢;另外一方面,循環間隔還要足夠長,這樣才能確保瀏覽器有能力渲染產生的變化。javascript

如何保證流暢呢?登登登登....該requestAnimationFrame登場了~~java

16.7ms的由來

大多數電腦顯示器的刷新頻率是60Hz,大概至關於每秒鐘重繪60次。大多數瀏覽器都會對重繪操做加以限制,不超過顯示器的重繪頻率,由於即便超過那個頻率用戶體驗也不會有提高。所以,最平滑動畫的最佳循環間隔是1000ms/60,約等於16.7msweb

settimeout->OUT

一樣的,顯示器16.7ms刷新間隔以前發生了其餘繪製請求(setTimeout),致使全部第三幀丟失,繼而致使動畫斷續顯示(堵車的感受),這就是過分繪製帶來的問題。不只如此,這種計時器頻率的下降也會對電池使用壽命形成負面影響,並會下降其餘應用的性能。瀏覽器

這也是爲什麼setTimeout的定時器值推薦最小使用16.7ms的緣由(16.7 = 1000 / 60, 即每秒60幀)。函數

但...

一、即便向其傳遞毫秒爲單位的參數,它們也不能達到ms的準確性。這是由於javascript是單線程的,可能會發生阻塞。性能

二、沒有對調用動畫的循環機制進行優化。優化

三、沒有考慮到繪製動畫的最佳時機,只是一味地以某個大體的事件間隔來調用循環。動畫

...OUT

requestAnimationFrame登場

requestAnimationFrame不須要使用者指定循環間隔時間,瀏覽器會基於當前頁面是否可見、CPU的負荷狀況等來自行決定最佳的幀速率,跟着瀏覽器的繪製走,若是瀏覽設備繪製間隔是16.7ms,那我就這個間隔繪製;若是瀏覽設備繪製間隔是10ms, 我就10ms繪製。
這樣天然就合理地使用CPU,不會存在過分繪製的問題,動畫不會掉幀,天然流暢的說~~spa

內部是這麼運做的:線程

瀏覽器(如頁面)每次重繪,就會通知我(requestAnimationFrame):嗨,我要重繪了,你能夠跟我一塊兒重繪哦!

這是資源很是高效的一種利用方式。怎麼講呢?

1.就算不少元素須要重繪,瀏覽器只要通知一次就能夠了。而setTimeout貌似是多個獨立繪製。

2.頁面最小化了,或者被Tab切換關燈了。頁面是不會重繪的,天然,requestAnimationFrame也不會洗澡的(沒通知啊)。頁面繪製所有中止,資源高效利用。

總結:

setTimeoutsetInterval都不精確。它們的內在運行機制決定了時間間隔參數實際上只是指定了把動畫代碼添加到瀏覽器UI線程隊列中以等待執行的時間。若是隊列前面已經加入了其餘任務,那動畫代碼就要等前面的任務完成後再執行。
requestAnimationFrame採用系統時間間隔,保持最佳繪製效率,不會由於間隔時間太短,形成過分繪製,增長開銷;也不會由於間隔時間太長,使用動畫卡頓不流暢,讓各類網頁動畫效果可以有一個統一的刷新機制,從而節省系統資源,提升系統性能,改善視覺效果。

特色

  • requestAnimationFrame會把每一幀中的全部DOM操做集中起來,在一次重繪或迴流中就完成,而且重繪或迴流的時間間隔牢牢跟隨瀏覽器的刷新頻率
  • 在隱藏或不可見的元素中,requestAnimationFrame將不會進行重繪或迴流,這固然就意味着更少的CPU、GPU和內存使用量
  • requestAnimationFrame是由瀏覽器專門爲動畫提供的API,在運行時瀏覽器會自動優化方法的調用,而且若是頁面不是激活狀態下的話,動畫會自動暫停,有效節省了CPU開銷

名詞解釋

<div class="bi-table">

<colgroup><col width="181px"><col width="521px"></colgroup>
<div data-type="p">動畫幀請求回調函數列表</div> <div data-type="p">每一個Document都有一個動畫幀請求回調函數列表,該列表能夠當作是由< handle, callback>元組組成的集合。其中handle是一個整數,惟一地標識了元組在列表中的位置;callback是一個無返回值的、形參爲一個時間值的函數(該時間值爲由瀏覽器傳入的從1970年1月1日到當前所通過的毫秒數)。 剛開始該列表爲空。</div>
<div data-type="p">Document</div> <div data-type="p">Dom模型中定義的Document節點</div>
<div data-type="p">Active document</div> <div data-type="p">瀏覽器上下文browsingContext中的Document被指定爲active document</div>
<div data-type="p">browsingContext</div> <div data-type="p">即瀏覽器上下文。</div><div data-type="p">瀏覽器上下文是呈現document對象給用戶的環境。 瀏覽器中的1個tab或一個窗口包含一個頂級瀏覽器上下文,若是該頁面有iframe,則iframe中也會有本身的瀏覽器上下文,稱爲嵌套的瀏覽器上下文。 </div>
<div data-type="p">頁面可見</div> <div data-type="p">當頁面被最小化或者被切換成後臺標籤頁時,頁面爲不可見,瀏覽器會觸發一個 visibilitychange事件,並設置document.hidden屬性爲true;切換到顯示狀態時,頁面爲可見,也一樣觸發一個 visibilitychange事件,設置document.hidden屬性爲false</div>
<div data-type="p">隊列</div> <div data-type="p">瀏覽器讓一個單線程共用於執行javascrip和更新用戶界面。這個線程一般被稱爲「瀏覽器UI線程」。 瀏覽器UI線程的工做基於一個簡單的隊列系統,任務會被保存到隊列中直到進程空閒。一旦空閒,隊列中的下一個任務就被從新提取出來並運行。這些任務要麼是運行javascript代碼,要麼執行UI更新,包括重繪和重排。</div>

</div>

API接口

Window對象定義瞭如下兩個接口:

partial interface Window {
  long requestAnimationFrame(FrameRequestCallback callback);
  void cancelAnimationFrame(long handle);
};

requestAnimationFrame

1.requestAnimationFrame方法用於通知瀏覽器重採樣動畫。

當requestAnimationFrame(callback)被調用時不會執行callback,而是會將元組< handle,callback>插入到動畫幀請求回調函數列表末尾(其中元組的callback就是傳入requestAnimationFrame的回調函數),而且返回handle值,該值爲瀏覽器定義的、大於0的整數,惟一標識了該回調函數在列表中位置。

2.每一個回調函數都有一個布爾標識cancelled,該標識初始值爲false,而且對外不可見。
3.在後面的「處理模型」 中咱們會看到,瀏覽器在執行「採樣全部動畫」的任務時會遍歷動畫幀請求回調函數列表,判斷每一個元組的callback的cancelled,若是爲false,則執行callback。

cancelAnimationFrame

一、cancelAnimationFrame 方法用於取消先前安排的一個動畫幀更新的請求。
二、當調用cancelAnimationFrame(handle)時,瀏覽器會設置該handle指向的回調函數的cancelled爲true。
不管該回調函數是否在動畫幀請求回調函數列表中,它的cancelled都會被設置爲true。
3.若是該handle沒有指向任何回調函數,則調用cancelAnimationFrame 不會發生任何事情。
4.注意:在requestAnimationFrame的callback內部執行cancelAnimationFrame不能取消動畫

處理模型

當頁面可見而且動畫幀請求回調函數列表不爲空時,瀏覽器會按期地加入一個「採樣全部動畫」的任務到UI線程的隊列中。

使用

requestAnimationFrame的用法與setTimeout很類似,只是不須要設置時間間隔而已。requestAnimationFrame使用一個回調函數做爲參數,這個回調函數會在瀏覽器重繪以前調用。它返回一個整數,表示定時器的編號,這個值能夠傳遞給cancelAnimationFrame用於取消這個函數的執行。

requestID = requestAnimationFrame(callback);

完美兼容

image.png | left | 580x293
若是想要簡單的兼容,能夠這樣子:

window.requestAnimFrame = (function(){
  return  window.requestAnimationFrame       ||
          window.webkitRequestAnimationFrame ||
          window.mozRequestAnimationFrame    ||
          function( callback ){
            window.setTimeout(callback, 1000 / 60);
          };
})();

可是呢,並非全部設備的繪製時間間隔是1000/60 ms, 以及上面並木有cancel相關方法,因此,就有下面這份更全面的兼容方法:

(function() {
    var lastTime = 0;
        var vendors = ['webkit', 'moz'];
        for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
            window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
            window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] ||    // Webkit中此取消方法的名字變了
                                          window[vendors[x] + 'CancelRequestAnimationFrame'];
        }
    if (!window.requestAnimationFrame) {
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16.7 - (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);
        };
    }
}());
相關文章
相關標籤/搜索