<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      深入理解requestAnimationFrame

      前言

      本文主要參考w3c資料,從底層實現原理的角度介紹了requestAnimationFrame、cancelAnimationFrame,給出了相關的示例代碼以及我對實現原理的理解和討論。

      本文介紹

      瀏覽器中動畫有兩種實現形式:通過申明元素實現(如SVG中的元素)和腳本實現。
      可以通過setTimeout和setInterval方法來在腳本中實現動畫,但是這樣效果可能不夠流暢,且會占用額外的資源。可參考《Html5 Canvas核心技術》中的論述:

      它們有如下的特征:
      1、即使向其傳遞毫秒為單位的參數,它們也不能達到ms的準確性。這是因為javascript是單線程的,可能會發生阻塞。
      2、沒有對調用動畫的循環機制進行優化。
      3、沒有考慮到繪制動畫的最佳時機,只是一味地以某個大致的事件間隔來調用循環。
      其實,使用setInterval或setTimeout來實現主循環,根本錯誤就在于它們抽象等級不符合要求。我們想讓瀏覽器執行的是一套可以控制各種細節的api,實現如“最優幀速率”、“選擇繪制下一幀的最佳時機”等功能。但是如果使用它們的話,這些具體的細節就必須由開發者自己來完成。

      requestAnimationFrame不需要使用者指定循環間隔時間,瀏覽器會基于當前頁面是否可見、CPU的負荷情況等來自行決定最佳的幀速率,從而更合理地使用CPU。

      本文主要內容

      名詞說明

      • 動畫幀請求回調函數列表

      每個Document都有一個動畫幀請求回調函數列表,該列表可以看成是由< handle, callback>元組組成的集合。其中handle是一個整數,唯一地標識了元組在列表中的位置;callback是一個無返回值的、形參為一個時間值的函數(該時間值為由瀏覽器傳入的從1970年1月1日到當前所經過的毫秒數)。
      剛開始該列表為空。

      • Document

      Dom模型中定義的Document節點。

      • Active document

      瀏覽器上下文browsingContext中的Document被指定為active document。

      • browsingContext

        瀏覽器上下文。

      瀏覽器上下文是呈現document對象給用戶的環境。
      瀏覽器中的1個tab或一個窗口包含一個頂級瀏覽器上下文,如果該頁面有iframe,則iframe中也會有自己的瀏覽器上下文,稱為嵌套的瀏覽器上下文。

      • DOM模型

      詳見我的理解DOM

      • document對象

      當html文檔加載完成后,瀏覽器會創建一個document對象。它對應于Document節點,實現了HTML的Document接口。
      通過該對象可獲得整個html文檔的信息,從而對HTML頁面中的所有元素進行訪問和操作。

      • HTML的Document接口

      該接口對DOM定義的Document接口進行了擴展,定義了 HTML 專用的屬性和方法。
      詳見The Document object

      • 頁面可見

      當頁面被最小化或者被切換成后臺標簽頁時,頁面為不可見,瀏覽器會觸發一個 visibilitychange事件,并設置document.hidden屬性為true;切換到顯示狀態時,頁面為可見,也同樣觸發一個 visibilitychange事件,設置document.hidden屬性為false。
      詳見Page VisibilityPage Visibility(頁面可見性) API介紹、微拓展

      • 隊列

      瀏覽器讓一個單線程共用于執行javascrip和更新用戶界面。這個線程通常被稱為“瀏覽器UI線程”。
      瀏覽器UI線程的工作基于一個簡單的隊列系統,任務會被保存到隊列中直到進程空閑。一旦空閑,隊列中的下一個任務就被重新提取出來并運行。這些任務要么是運行javascript代碼,要么執行UI更新,包括重繪和重排。

      API接口

      Window對象定義了以下兩個接口:

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

      requestAnimationFrame

      requestAnimationFrame方法用于通知瀏覽器重采樣動畫。
      當requestAnimationFrame(callback)被調用時不會執行callback,而是會將元組< handle,callback>插入到動畫幀請求回調函數列表末尾(其中元組的callback就是傳入requestAnimationFrame的回調函數),并且返回handle值,該值為瀏覽器定義的、大于0的整數,唯一標識了該回調函數在列表中位置。
      每個回調函數都有一個布爾標識cancelled,該標識初始值為false,并且對外不可見。
      在后面的“處理模型”中我們會看到,瀏覽器在執行“采樣所有動畫”的任務時會遍歷動畫幀請求回調函數列表,判斷每個元組的callback的cancelled,如果為false,則執行callback。

      cancelAnimationFrame

      cancelAnimationFrame 方法用于取消先前安排的一個動畫幀更新的請求。
      當調用cancelAnimationFrame(handle)時,瀏覽器會設置該handle指向的回調函數的cancelled為true。
      無論該回調函數是否在動畫幀請求回調函數列表中,它的cancelled都會被設置為true。
      如果該handle沒有指向任何回調函數,則調用cancelAnimationFrame 不會發生任何事情。

      處理模型

      當頁面可見并且動畫幀請求回調函數列表不為空時,瀏覽器會定期地加入一個“采樣所有動畫”的任務到UI線程的隊列中。

      此處使用偽代碼來說明“采樣所有動畫”任務的執行步驟:

      var list = {};
      
      var browsingContexts = 瀏覽器頂級上下文及其下屬的瀏覽器上下文;
      
      for (var browsingContext in browsingContexts) {
      /*!將時間值從 DOMTimeStamp 更改為 DOMHighResTimeStamp 是 W3C 針對基于腳本動畫計時控制規范的最新編輯草案中的最新更改,并且某些供應商仍將其作為 DOMTimeStamp 實現。較早版本的 W3C 規范使用 DOMTimeStamp,允許你將 Date.now 用于當前時間。
      如上所述,某些瀏覽器供應商可能仍實現 DOMTimeStamp 參數,或者尚未實現 window.performance.now 計時函數。因此需要用戶進行polyfill
      */
          //var time = 從1970年1月1日到當前所經過的毫秒數;
          var time = DOMHighResTimeStamp   //從頁面導航開始時測量的高精確度時間。DOMHighResTimeStamp 以毫秒為單位,精確到千分之一毫秒。此時間值不直接與 Date.now() 進行比較,后者測量自 1970 年 1 月 1 日至今以毫秒為單位的時間。如果你希望將 time 參數與當前時間進行比較,請使用當前時間的 window.performance.now。
      
      
      
         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) {    
                  }
              }
          }
      }
      

      已解決的問題

      • 為什么在callback內部執行cancelAnimationFrame不能取消動畫?

      問題描述
      如下面的代碼會一直執行a:

          var id = null;
      
          function a(time) {
              console.log("animation");
              window.cancelAnimationFrame(id);    //不起作用
              id = window.requestAnimationFrame(a);
          }
      
          a();
      

      原因分析
      我們來分析下這段代碼是如何執行的:
      1、執行a
      (1)執行“a();”,執行函數a;
      (2)執行“console.log("animation");”,打印“animation”;
      (3)執行“window.cancelAnimationFrame(id);”,因為id為null,瀏覽器在動畫幀請求回調函數列表中找不到對應的callback,所以不發生任何事情;
      (4)執行“id = window.requestAnimationFrame(a);”,瀏覽器會將一個元組< handle, a>插入到Document的動畫幀請求回調函數列表末尾,將id賦值為該元組的handle值;

      2、a執行完畢后,執行第一個“采樣所有動畫”的任務
      假設當前頁面一直可見,因為動畫幀請求回調函數列表不為空,所以瀏覽器會定期地加入一個“采樣所有動畫”的任務到線程隊列中。
      a執行完畢后的第一個“采樣所有動畫”的任務執行時會進行以下步驟:
      (1)拷貝Document的動畫幀請求回調函數列表到list變量中,清空Document的動畫幀請求回調函數列表;
      (2)遍歷list的列表,列表有1個元組,該元組的callback為a;
      (3)判斷a的cancelled,為默認值false,所以執行a;
      (4)執行“console.log("animation");”,打印“animation”;
      (5)執行“window.cancelAnimationFrame(id);”,此時id指向當前元組的a(即當前正在執行的a),瀏覽器將當前元組的a的cancelled設為true。
      (6)執行“id = window.requestAnimationFrame(a);”,瀏覽器會將新的元組< handle, a>插入到Document的動畫幀請求回調函數列表末尾(新元組的a的cancelled為默認值false),將id賦值為該元組的handle值。

      3、執行下一個“采樣所有動畫”的任務
      當下一個“采樣所有動畫”的任務執行時,會判斷動畫幀請求回調函數列表的元組的a的cancelled,因為該元組為新插入的元組,所以值為默認值false,因此會繼續執行a。
      如此類推,瀏覽器會一直循環執行a。

      解決方案
      有下面兩個方案:
      1、執行requestAnimationFrame之后再執行cancelAnimationFrame。
      下面代碼只會執行一次a:

          var id = null;
      
          function a(time) {
              console.log("animation");
              id = window.requestAnimationFrame(a);
              window.cancelAnimationFrame(id);
          }
      
          a();
      

      2、在callback外部執行cancelAnimationFrame。
      下面代碼只會執行一次a:

          function a(time) {
              console.log("animation");
              id = window.requestAnimationFrame(a);
          }
      
          a();
          window.cancelAnimationFrame(id);
      

      因為執行“window.cancelAnimationFrame(id);”時,id指向了新插入到動畫幀請求回調函數列表中的元組的a,所以 “采樣所有動畫”任務判斷元組的a的cancelled時,該值為true,從而不再執行a。

      注意事項

      1、在處理模型中我們已經看到,在遍歷執行拷貝的動畫幀請求回調函數列表中的回調函數之前,Document的動畫幀請求回調函數列表已經被清空了。因此如果要多次執行回調函數,需要在回調函數中再次調用requestAnimationFrame將包含回調函數的元組加入到Document的動畫幀請求回調函數列表中,從而瀏覽器才會再次定期加入“采樣所有動畫”的任務(當頁面可見并且動畫幀請求回調函數列表不為空時,瀏覽器才會加入該任務),執行回調函數。

      例如下面代碼只執行1次animate函數:

                   var id = null;
                   
                   function animate(time) {
                       console.log("animation");
                   }
      
                    window.requestAnimationFrame(animate);
      

      下面代碼會一直執行animate函數:

          var id = null;
      
          function animate(time) {
              console.log("animation");
              window.requestAnimationFrame(animate);
          }
          animate();
      

      2、如果在執行回調函數或者Document的動畫幀請求回調函數列表被清空之前多次調用requestAnimationFrame插入同一個回調函數,那么列表中會有多個元組指向該回調函數(它們的handle不同,但callback都為該回調函數),“采集所有動畫”任務會執行多次該回調函數。

      例如下面的代碼在執行“id1 = window.requestAnimationFrame(animate);”和“id2 = window.requestAnimationFrame(animate);”時會將兩個元組(handle分別為id1、id2,回調函數callback都為animate)插入到Document的動畫幀請求回調函數列表末尾。
      因為“采樣所有動畫”任務會遍歷執行動畫幀請求回調函數列表的每個回調函數,所以在“采樣所有動畫”任務中會執行兩次animate。

          //下面代碼會打印兩次"animation"
          
          var id1 = null,
              id2 = null;
          function animate(time) {
              console.log("animation");
          }
          
          id1 = window.requestAnimationFrame(animate);
          id2 = window.requestAnimationFrame(animate);   //id1和id2值不同,指向列表中不同的元組,這兩個元組中的callback都為同一個animate
      

      兼容性方法

      下面為《HTML5 Canvas 核心技術》給出的兼容主流瀏覽器的requestNextAnimationFrame 和cancelNextRequestAnimationFrame方法,大家可直接拿去用:

      window.requestNextAnimationFrame = (function () {
          var originalRequestAnimationFrame = undefined,
              wrapper = undefined,
              callback = undefined,
              geckoVersion = null,
              userAgent = navigator.userAgent,
              index = 0,
              self = this;
      
          wrapper = function (time) {
              time = performance.now();
              self.callback(time);
          };
      
          /*!
           bug!
           below code:
           when invoke b after 1s, will only invoke b, not invoke a!
      
           function a(time){
           console.log("a", time);
           webkitRequestAnimationFrame(a);
           }
      
           function b(time){
           console.log("b", time);
           webkitRequestAnimationFrame(b);
           }
      
           a();
      
           setTimeout(b, 1000);
      
      
      
           so use requestAnimationFrame priority!
           */
          if(window.requestAnimationFrame) {
              return requestAnimationFrame;
          }
      
      
          // Workaround for Chrome 10 bug where Chrome
          // does not pass the time to the animation function
      
          if (window.webkitRequestAnimationFrame) {
              // Define the wrapper
      
              // Make the switch
      
              originalRequestAnimationFrame = window.webkitRequestAnimationFrame;
      
              window.webkitRequestAnimationFrame = function (callback, element) {
                  self.callback = callback;
      
                  // Browser calls the wrapper and wrapper calls the callback
      
                  return originalRequestAnimationFrame(wrapper, element);
              }
          }
      
          //修改time參數
          if (window.msRequestAnimationFrame) {
              originalRequestAnimationFrame = window.msRequestAnimationFrame;
      
              window.msRequestAnimationFrame = function (callback) {
                  self.callback = callback;
      
                  return originalRequestAnimationFrame(wrapper);
              }
          }
      
          // Workaround for Gecko 2.0, which has a bug in
          // mozRequestAnimationFrame() that restricts animations
          // to 30-40 fps.
      
          if (window.mozRequestAnimationFrame) {
              // Check the Gecko version. Gecko is used by browsers
              // other than Firefox. Gecko 2.0 corresponds to
              // Firefox 4.0.
      
              index = userAgent.indexOf('rv:');
      
              if (userAgent.indexOf('Gecko') != -1) {
                  geckoVersion = userAgent.substr(index + 3, 3);
      
                  if (geckoVersion === '2.0') {
                      // Forces the return statement to fall through
                      // to the setTimeout() function.
      
                      window.mozRequestAnimationFrame = undefined;
                  }
              }
          }
      
          return window.webkitRequestAnimationFrame ||
              window.mozRequestAnimationFrame ||
              window.oRequestAnimationFrame ||
              window.msRequestAnimationFrame ||
      
              function (callback, element) {
                  var start,
                      finish;
      
                  window.setTimeout(function () {
                      start = performance.now();
                      callback(start);
                      finish = performance.now();
      
                      self.timeout = 1000 / 60 - (finish - start);
      
                  }, self.timeout);
              };
      })();
      
      
          window.cancelNextRequestAnimationFrame = window.cancelRequestAnimationFrame
              || window.webkitCancelAnimationFrame
              || window.webkitCancelRequestAnimationFrame
              || window.mozCancelRequestAnimationFrame
              || window.oCancelRequestAnimationFrame
              || window.msCancelRequestAnimationFrame
              || clearTimeout;
      

      參考資料

      Timing control for script-based animations
      Browsing contexts
      The Document object
      《HTML5 Canvas核心技術》
      理解DOM
      Page Visibility
      Page Visibility(頁面可見性) API介紹、微拓展
      HOW BROWSERS WORK: BEHIND THE SCENES OF MODERN WEB BROWSERS

      posted @ 2014-09-07 10:25  楊元超  閱讀(11477)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 日韩一区二区三区精品| 国产区精品福利在线熟女| 东京热一精品无码av| 中文国产成人精品久久不卡 | 樱花草视频www日本韩国| 临猗县| 亚洲国产综合av在线观看| 亚洲国产美国产综合一区| 天天躁日日躁狠狠躁中文字幕| 欧美不卡无线在线一二三区观| 在线观看免费人成视频色| 国产精品麻豆成人AV电影艾秋| 乱熟女高潮一区二区在线| 极品尤物被啪到呻吟喷水| 久久这里有精品国产电影网| 日本高清aⅴ毛片免费| 精品国产av无码一区二区三区| 亚洲高清 一区二区三区| 海宁市| 无码一区二区三区视频| 亚洲国产欧美一区二区好看电影| 康马县| 免费国产高清在线精品一区| 成人av一区二区三区| 麻花传媒在线观看免费| 国产一区二区三区在线观| 国产成人精品亚洲日本语言| 亚洲精品无码久久一线| 熟妇高潮精品一区二区三区 | 思热99re视热频这里只精品| 在线aⅴ亚洲中文字幕| 国产精品久久国产精麻豆99网站| 黑人巨茎大战白人美女| 麻豆麻豆麻豆麻豆麻豆麻豆| 99中文字幕精品国产| 嘉义市| 蜜芽久久人人超碰爱香蕉| 昭苏县| 久久精品免视看成人国产| 久久99热只有频精品8| 色吊丝一区二区中文字幕|