requestAnimationFrame用法

    在Web應用中,實現動畫效果的方法比較多,Javascript 中能夠經過定時器 setTimeout 來實現,css3 可使用 transition animation 來實現,html5 中的 canvas 也能夠實現。除此以外,html5 還提供一個專門用於請求動畫的API,那就是 requestAnimationFrame,顧名思義就是請求動畫幀。 javascript

1、相關概念

爲了理解 requestAnimationFrame 背後的原理,咱們首先須要瞭解一下與之相關的幾個概念:css

1.  頁面可見

    當頁面被最小化或者被切換成後臺標籤頁時,頁面爲不可見,瀏覽器會觸發一個 visibilitychange事件,並設置document.hidden屬性爲true;切換到顯示狀態時,頁面爲可見,也一樣觸發一個 visibilitychange事件,設置document.hidden屬性爲falsehtml

2.  動畫幀請求回調函數列表

    每一個Document都有一個動畫幀請求回調函數列表,該列表能夠當作是由<handlerId, callback>元組組成的集合。其中handlerId是一個整數,惟一地標識了元組在列表中的位置;callback是回調函數。html5

3.  屏幕刷新頻率

    即圖像在屏幕上更新的速度,也即屏幕上的圖像每秒鐘出現的次數,它的單位是赫茲(Hz)。 對於通常筆記本電腦,這個頻率大概是60Hz, 這個值的設定受屏幕分辨率、屏幕尺寸和顯卡的影響。java

4.  動畫原理

    根據上面的原理咱們知道,你眼前所看到圖像正在以每秒60次的頻率刷新,因爲刷新頻率很高,所以你感受不到它在刷新。而動畫本質就是要讓人眼看到圖像被刷新而引發變化的視覺效果,這個變化要以連貫的、平滑的方式進行過渡。 那怎麼樣才能作到這種效果呢?css3

    刷新頻率爲60Hz的屏幕每16.7ms刷新一次,咱們在屏幕每次刷新前,將圖像的位置向左移動一個像素,即1px。這樣一來,屏幕每次刷出來的圖像位置都比前一個要差1px,所以你會看到圖像在移動;因爲咱們人眼的視覺停留效應,當前位置的圖像停留在大腦的印象還沒消失,緊接着圖像又被移到了下一個位置,所以你纔會看到圖像在流暢的移動,這就是視覺效果上造成的動畫。git

2、 requestAnimationFrame用法

異步,傳入的函數在重繪以前調用。
github

1.  寫法:handlerId = requestAnimationFrame(callback)

(1)  傳入一個callback函數,即動畫函數;web

(2)  返回值handlerId爲瀏覽器定義的、大於0的整數,惟一標識了該回調函數在列表中位置。canvas

2.  瀏覽器執行過程:

(1)  首先要判斷document.hidden屬性是否爲true,即頁面處於可見狀態下才會執行;

(2)  瀏覽器清空上一輪的動畫函數;

(3)  這個方法返回的handlerId 值會和動畫函數callback,以<handlerId , callback> 進入到動畫幀請求回調函數列;

(4)  瀏覽器會遍歷動畫幀請求回調函數列表,根據handlerId 的值大小,依次去執行相應的動畫函數。

3.  取消動畫函數的方法:

cancelAnimationFrame(handlerId)複製代碼

3、setTimeout與requestAnimationFrame

1.  setTimeout

    理解了上面的概念之後,咱們不難發現,setTimeout 其實就是經過設置一個間隔時間來不斷的改變圖像的位置,從而達到動畫效果的。但利用seTimeout實現的動畫在某些低端機上會出現卡頓、抖動的現象。 這種現象的產生有兩個緣由:

  • setTimeout的執行時間並非肯定的。在Javascript中, setTimeout 任務被放進了異步隊列中,只有當主線程上的任務執行完之後,纔會去檢查該隊列裏的任務是否須要開始執行,所以 setTimeout 的實際執行時間通常要比其設定的時間晚一些。
  • 刷新頻率受屏幕分辨率和屏幕尺寸的影響,所以不一樣設備的屏幕刷新頻率可能會不一樣,而 setTimeout只能設置一個固定的時間間隔,這個時間不必定和屏幕的刷新時間相同。

以上兩種狀況都會致使setTimeout的執行步調和屏幕的刷新步調不一致,從而引發丟幀現象。 那爲何步調不一致就會引發丟幀呢?

首先要明白,setTimeout的執行只是在內存中對圖像屬性進行改變,這個變化必需要等到屏幕下次刷新時纔會被更新到屏幕上。若是二者的步調不一致,就可能會致使中間某一幀的操做被跨越過去,而直接更新下一幀的圖像。假設屏幕每隔16.7ms刷新一次,而setTimeout每隔10ms設置圖像向左移動1px, 就會出現以下繪製過程:

  • 第0ms:  屏幕未刷新,等待中,setTimeout也未執行,等待中;
  • 第10ms:  屏幕未刷新,等待中,setTimeout開始執行並設置圖像屬性left=1px;
  • 第16.7ms:  屏幕開始刷新,屏幕上的圖像向左移動了1pxsetTimeout 未執行,繼續等待中;
  • 第20ms:  屏幕未刷新,等待中,setTimeout開始執行並設置left=2px;
  • 第30ms:  屏幕未刷新,等待中,setTimeout開始執行並設置left=3px;
  • 第33.4ms: 屏幕開始刷新,屏幕上的圖像向左移動了3pxsetTimeout未執行,繼續等待中;

從上面的繪製過程當中能夠看出,屏幕沒有更新left=2px的那一幀畫面,圖像直接從1px的位置跳到了3px的的位置,這就是丟幀現象,這種現象就會引發動畫卡頓。

2.  requestAnimationFrame

setTimeout相比,requestAnimationFrame最大的優點是由系統來決定回調函數的執行時機。具體一點講,若是屏幕刷新率是60Hz,那麼回調函數就每16.7ms被執行一次,若是刷新率是75Hz,那麼這個時間間隔就變成了1000/75=13.3ms,換句話說就是,requestAnimationFrame的步伐跟着系統的刷新步伐走。它能保證回調函數在屏幕每一次的刷新間隔中只被執行一次,這樣就不會引發丟幀現象,也不會致使動畫出現卡頓的問題。

這個API的調用很簡單,以下所示:

var progress = 0;
//回調函數
function render() {  
  progress += 1; //修改圖像的位置 
  if (progress < 100) {  //在動畫沒有結束前,遞歸渲染 
    window.requestAnimationFrame(render); 
  }
}
//第一幀渲染
window.requestAnimationFrame(render);複製代碼

除此以外,requestAnimationFrame還有如下兩個優點:

  • CPU節能:使用setTimeout實現的動畫,當頁面被隱藏或最小化時,setTimeout 仍然在後臺執行動畫任務,因爲此時頁面處於不可見或不可用狀態,刷新動畫是沒有意義的,徹底是浪費CPU資源。而requestAnimationFrame則徹底不一樣,當頁面處理未激活的狀態下,該頁面的屏幕刷新任務也會被系統暫停,所以跟着系統步伐走的requestAnimationFrame也會中止渲染,當頁面被激活時,動畫就從上次停留的地方繼續執行,有效節省了CPU開銷。
  • 函數節流:在高頻率事件(resize,scroll等)中,爲了防止在一個刷新間隔內發生屢次函數執行,使用requestAnimationFrame可保證每一個刷新間隔內,函數只被執行一次,這樣既能保證流暢性,也能更好的節省函數執行的開銷。一個刷新間隔內函數執行屢次時沒有意義的,由於顯示器每16.7ms刷新一次,屢次繪製並不會在屏幕上體現出來。

3.  優雅降級

    因爲requestAnimationFrame目前還存在兼容性問題,並且不一樣的瀏覽器還須要帶不一樣的前綴。所以須要經過優雅降級的方式對requestAnimationFrame進行封裝,優先使用高級特性,而後再根據不一樣瀏覽器的狀況進行回退,直至只能使用setTimeout的狀況。下面的代碼就是有人在github上提供的polyfill,詳細介紹請參考github代碼 requestAnimationFrame

if (!Date.now)
    Date.now = function() { return new Date().getTime(); };
 
(function() {
 'use strict';
     
    var vendors = ['webkit', 'moz'];
    for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {
        var vp = vendors[i];
        window.requestAnimationFrame = window[vp+'RequestAnimationFrame'];
        window.cancelAnimationFrame = (window[vp+'CancelAnimationFrame']
                                   || window[vp+'CancelRequestAnimationFrame']);
    }
    if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) // iOS6 is buggy
        || !window.requestAnimationFrame || !window.cancelAnimationFrame) {
        var lastTime = 0;
        window.requestAnimationFrame = function(callback) {
            var now = Date.now();
            var nextTime = Math.max(lastTime + 16, now);
            return setTimeout(function() { callback(lastTime = nextTime); },
                              nextTime - now);
        };
        window.cancelAnimationFrame = clearTimeout;
    }
}());
複製代碼


(文章純屬我的備忘記錄用途,部分引用來自網上加上我的理解整理。歡迎轉載,請註明出處。如對你有幫助,請隨意打賞。)

相關文章
相關標籤/搜索