requestAnimationFrame()詳解

admin發表於2017-04-15

關於requestAnimationFrame()基本用法和概念可以參閱requestAnimationFrame()用法一章節。

下面再從更為底層的層面來對此方法進行一下介紹,感興趣的朋友可以做一下參考。

回撥函式列表:

每一個document物件都有一個動畫幀請求回撥函式列表;該列表中儲存著由<handle, callback>組成的集合。

(1).callback:它就是傳遞給requestAnimationFrame()方法的回撥函式;此函式沒有返回值,引數(可以省略)是一個時間值(該值由瀏覽器傳入,從1970年1月1日到當前所經過的毫秒數)。

(2).handle:是一個整數,唯一地標識了元組在列表中的位置,cancelAnimationFrame()可以通過它來停止動畫。

瀏覽器UI執行緒:

此執行緒用於執行javascript或者介面的更新。

基於一個簡單的佇列系統,任務會被儲存到佇列中直到程式空閒。

一旦空閒,佇列中的下一個任務就被重新提取出來並執行。

這些任務要麼是執行javascript程式碼,要麼執行UI更新,包括重繪和重排。

頁面是否啟用:

當頁面被最小化或者被切換成非當前選項卡時,頁面為不可見或者說沒有啟用,瀏覽器會觸發visibilitychange事件,並設定document.hidden屬性為true;切換到顯示狀態時,頁面為可見,也同樣觸發一個 visibilitychange事件,設定document.hidden屬性為false。

requestAnimationFrame:

此方法可以將回撥函式追加到動畫幀請求回撥函式列表的末尾。

當執行requestAnimationFrame(callback)時候,不會立刻呼叫callback函式,只是將其放入佇列。

每個回撥函式都有一個布林標識cancelled,該標識初始值為false,並且對外不可見;當瀏覽器再執行列表中的回撥函式的時候,判斷每個元組的callback的cancelled,如果為false,則執行callback。

cancelAnimationFrame:

方法能夠取消一個動畫幀更新的請求。

當呼叫cancelAnimationFrame(handle)時,瀏覽器會設定該handle指向的回撥函式的cancelled為true。

無論該回撥函式是否在動畫幀請求回撥函式列表中,它的cancelled都會被設定為true。

如果該handle沒有指向任何回撥函式,則呼叫cancelAnimationFrame 不會發生任何事情。

處理模型:

當頁面可見並且動畫幀請求回撥函式列表不為空時,瀏覽器會定期獎這些回撥函式加入到UI執行緒的佇列中。

此處使用虛擬碼來說明“取樣所有動畫”任務的執行步驟:

[JavaScript] 純文字檢視 複製程式碼
var list = {};
var browsingContexts = 瀏覽器頂級上下文及其下屬的瀏覽器上下文;
 
for (var browsingContext in browsingContexts) {
 
  //var time = 從1970年1月1日到當前所經過的毫秒數;
  var time = DOMHighResTimeStamp 
 
  var d = browsingContext的active document; //即當前瀏覽器上下文中的Document節點
  //如果該active document可見
  if (d.hidden !== true) {
    //拷貝active document的動畫幀請求回撥函式列表到list中,並清空該列表
    var doclist = d的動畫幀請求回撥函式列表
    doclist.appendTo(list);
    clear(doclist);
  }
 
  //遍歷動畫幀請求回撥函式列表的元組中的回撥函式
  for (var callback in list) {
    if (callback.cancelled !== true) {
      try {
        //每個browsingContext都有一個對應WindowProxy,WindowProxy物件會將callback指向active document關聯的window。
        //傳入時間值time
        callback.call(window, time);
      }
      //忽略異常
      catch (e) {}
    }
  }
}

看一段程式碼例項:

[JavaScript] 純文字檢視 複製程式碼
var id = null;
function func(time) {
  console.log("螞蟻部落");
  window.cancelAnimationFrame(id);
  id = window.requestAnimationFrame(func);
}
func();

我們的目的是是函式只執行一次,但是結果卻是會無限迴圈下去,下面做一下分析:

(1).func()呼叫之後,當執行到window.cancelAnimationFrame(id),id是null,所以它沒有取消任何動畫請求幀回撥函式的執行。

(2).id = window.requestAnimationFrame(func),又將一個新的回撥函式追加到佇列。

(3).所以func又將會被加入UI執行緒的佇列中,func()又將被執行;雖然window.cancelAnimationFrame(id)的引數這次不會null,但是與此id對應的回撥函式已經執行了,程式碼修改如下:

[JavaScript] 純文字檢視 複製程式碼
var id = null;
function func(time) {
  console.log("螞蟻部落");
  id = window.requestAnimationFrame(func);
  window.cancelAnimationFrame(id);
}
func();

只要將程式碼的順序對調一下即可。

相關文章