requestAnimationFrame

requestAnimationFrame()

window.requestAnimationFrame()

request 請求 Animation 動畫 Frame 框架。 一個瀏覽器的方法。 用顯示器更新機制,幫忙做「動畫更新時機」最佳化,使其符合瀏覽器更新的頻率。

window.requestAnimationFrame() 方法跟 setTimeout 类似,都是推迟某个函数的执行。 不同之处在于,setTimeout 必须指定推迟的时间, window.requestAnimationFrame() 则是推迟到浏览器「下一次重流时」执行, 重绘通常是 16ms 执行一次,不过浏览器会自动调节这个速率。

如果一個瀏覽器標籤頁面中,運行一個動畫, 當這個標籤頁不可見時,瀏覽器會暫停它,這會減少 CPU、內存的壓力,節省電池電量。

如果某个函数会改变网页的布局,一般就放在 window.requestAnimationFrame() 里面执行, 这样可以节省系统资源,使得网页效果更加平滑。 因为慢速设备会用较慢的速率重流和重绘,而速度更快的设备会有更快的速率。

window.requestAnimationFrame(callback);

上面 callback() 會接收一個 "参数", 就是系统传入的一个高精度时间戳( performance.now() 的返回值), 单位是毫秒,表示距离网页加载的时间。

var myId = window.requestAnimationFrame( mycallback ) myId 會得到一个整数, 这个整数传入 window.cancelAnimationFrame(myId),用来取消 mycallback 的执行。

var element = document.getElementById('animate');
element.style.position = 'absolute';

var start = null;
function step(timestamp) {
  // 會接收到一個 window.requestAnimationFrame() 傳來的時間戳參數 
  if (!start) start = timestamp;
  var progress = timestamp - start;
  // 新的時間點 - 第一次執行的時間點 = 每次越來越長的時間長度。

  // 希望左移元素,最大不超过 200 像素:兩個數中間取小的作為位置值。
  element.style.left = Math.min(progress / 10, 200) + 'px';

  // 如果距离 "第一次执行時間點" 不超过 2000 毫秒
  if (progress < 2000) {
    window.requestAnimationFrame(step);
    // 就继续执行动画
  }
}

// 優化執行 `step()` (配合螢幕更新時機)
window.requestAnimationFrame(step);

*基於 script 動畫的時間控制 script ("requestAnimationFrame") Timing control for script-based animations ("requestAnimationFrame") https://msdn.microsoft.com/zh-tw/library/hh920765(v=vs.85).aspx

MDN
https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/dev-guides/hh920765(v=vs.85)

requestAnimationFrame() 把能夠合併的動作, 放在一個渲染週期內完成,從而呈現出更流暢的動畫效果, 例如你原使用 setTimrout 做動畫, 改為使用 requestAnimationFrame 替換 setTimeout, 可使 JS 動畫能夠和 CSS transition 或 SVG SMIL 動畫同步發生。

目前主流浏览器 ( Firefox 23 / IE 10 / Chrome / Safari )都支持这个方法。 可以用下面的方法,检查浏览器是否支持这个 API。 如果不支持,则自行模拟部署该方法。

動畫計算要時間, 假如我們設定在 "瀏覽器尺寸變動" 時,去觸發一個動畫計算, 因為「尺寸變動」是根據瀏覽器更新,動畫因此密集地在重新計算, 但只有在最後一次,才會真正運算完,得到結果。 為了避免前面重複計算浪費效能,下面要做一個"節流產生器"。

我們將做一個自定義事件, 當觸發這個自定義事件時,才執行「動畫計算」, 而在動畫計算完成前,不能重複觸發這個自定義事件。

我們讓這個自定義事件,跟著瀏覽器的事件觸發: 例如我們監聽滾動,當滾動事件被觸發, 同時也會試著觸發這個自定義事件, 但是除非當前沒有「動畫計算」,事件才會再次被觸發、 才會執行新的「動畫計算」。

(function() {

  // ### 1. 建立 window.requestAnimFrame 的後退機制用 setTimeout
  window.requestAnimFrame = (function() {
    return (
      window.requestAnimationFrame ||
      window.webkitRequestAnimationFrame ||
      window.mozRequestAnimationFrame ||
      window.oRequestAnimationFrame ||
      window.msRequestAnimationFrame ||
      function(callback) {
        window.setTimeout(callback, 1000 / 60);
      }
    );
  })();

  // ### 2. throttle
  var throttle = function(type, name, obj) {
    obj = obj || window;
    var running = false;
    var func = function() {
      if (running) return;
      running = true;
      requestAnimFrame(function() {
        obj.dispatchEvent(new CustomEvent(name));
        running = false;
      });
    };
    obj.addEventListener(type, func);
  };

  // ### 3. 註冊事件
  throttle('resize', 'optimizedResize');
  throttle('scroll', 'optimizedScroll');
})();

// ### 4. 監聽到執行動作
window.addEventListener('optimizedResize', function() {
  console.log('optimizedResize');
});
window.addEventListener('optimizedScroll', function() {
  console.log('optimizedScroll');
});

解釋

1. window.requestAnimFrame

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

window.requestAnimFrame window 物件中,找到 (沒有的話就建立) 一個叫 requestAnimFrame 的 key。

= (function() { ... })() 將一個立即函式指派給這個 key。立即函式,key 的值為 return 的東西

window.requestAnimationFrame || 取得 window 物件中的 requestAnimationFrame,沒有就

window.webkitRequestAnimationFrame || ... 找 webkitRequestAnimationFrame... 是各 (webkit, ff, opera, ms) 瀏覽器中有無提供方法 (若是新瀏覽器都有提供),

function(callback) { ... } 最後沒有的話代表是就瀏覽器,那只好做一個函式: 用 setTimeout ,做一個會定時執行 callback,大多數監視器顯示頻率為 16.7ms, (小知識: setTimeout 最低是 4ms ,再低也是 4 ms)...

2. throttle

下面會建立一個控制機制 throttle:

// ### 2. throttle
// 要監聽的事件 (type)、自定義事件的名稱 (name)、要安裝監聽器的對象 (obj)
var throttle = function(type, name, obj) {
  obj = obj || window
  // 沒有輸入 obj 參數,就指定為 window
  var running = false
  // 預設是未執行

  // func 是一個「不會被重複執行」的機制
  var func = function() {
    if (running) return

    // 通知此 throttle 開始
    running = true

    // 當符合的時機
    requestAnimFrame(function() {

      // 建立「一個命名為 name 的自定義事件」
      // 並觸發 obj 身上的 name 事件
      obj.dispatchEvent(new CustomEvent(name))

      // 通知結束 throttle
      running = false
    })
  }

  // obj 監聽 type 事件,發生時,執行 func 
  obj.addEventListener(type, func)
}

3. 註冊事件

// ### 3. 註冊事件
throttle('resize', 'optimizedResize')
throttle('scroll', 'optimizedScroll')

監聽 window 的 resize 事件,若符合更新時機, 建立 'optimizedResize' 自定義事件

4. 監聽到執行動作

window 會不斷監聽 scroll 事件, 當同時又監聽到 'optimizedResize' 自定義事件時, 才可做某些事情 而 'optimizedResize' 自定義事件不會重複被觸發。

// ### 4. 監聽到就執行動作
window.addEventListener('optimizedResize', function() {
  console.log('optimizedResize')
})
window.addEventListener('optimizedScroll', function() {
  console.log('optimizedScroll')
})

results for ""

    No results matching ""