深切明白HTML5定时器requestAnimationFrame

媒介

计时器一直是javascript动画的核心技术。而编写动画轮回的关键是要知道延迟时候多长适宜。一方面,轮回距离必需充足短,如许才让差别的动画结果显得腻滑流通;另一方面,轮回距离还要充足长,如许才确保浏览器有才能衬着发生的变化。

大多数电脑显示器的革新频次是60Hz,也许相当于每秒钟重绘60次。大多数浏览器都邑对重绘操纵加以限定,不凌驾显示器的重绘频次,由于纵然凌驾谁人频次用户体验也不会有提拔。因而,最腻滑动画的最好轮回距离是1000ms/60,约等于16.6ms。

而setTimeout和setInterval的题目是,它们都不准确。它们的内涵运行机制决议了时候距离参数实际上只是指定了把动画代码添加到浏览器UI线程行列中以守候实行的时候。假如行列前面已加入了其他使命,那动画代码就要等前面的使命完成后再实行。

requestAnimationFrame采纳体系时候距离,坚持最好绘制效力,不会由于距离时候太短,形成过分绘制,增添开支;也不会由于距离时候太长,运用动画卡顿不流通,让种种网页动画结果能够有一个一致的革新机制,从而节约体系资源,进步体系机能,改良视觉结果。

特性

  • requestAnimationFrame会把每一帧中的一切DOM操纵集合起来,在一次重绘或回流中就完成,而且重绘或回流的时候距离牢牢追随浏览器的革新频次。
  • 在隐蔽或不可见的元素中,requestAnimationFrame将不会举行重绘或回流,这固然就意味着更少的CPU、GPU和内存运用量。
  • requestAnimationFrame是由浏览器特地为动画供应的API,在运行时浏览器会自动优化要领的挪用,而且假如页面不是激活状态下的话,动画会自动停息,有用节约了CPU开支。

用法

requestAnimationFrame要领将一个callback作为参数,回调函数会被传入一个参数,DOMHighResTimeStamp,指导当前被 requestAnimationFrame() 排序的回调函数被触发的时候。返回值是一个要求 ID,示意回调列表中唯一的标识。能够传这个值给 window.cancelAnimationFrame() 以作废回调函数。

requestID = window.requestAnimationFrame(callback);

运用这个api,能够将某些代码放到下一次从新衬着时实行,防止短时候内触发大批reflow。
比方页面转动事宜(scroll)的回调函数就很合适运用这个api, 将回调操纵推晚到下一次从新衬着。但须要注重的是requestAnimationFrame 不治理回调函数,即在回调被实行前,屡次挪用带有统一回调函数的 requestAnimationFrame,会致使回调在统一帧中实行屡次。最简朴的,能够用撙节函数来处理这个题目,也能够想办法让requestAnimationFrame的行列里一样的回调函数只要一个:

let scheduledAnimationFrame = false;
document.body.onscroll = () => {
    if (scheduledAnimationFrame) return;
    scheduledAnimationFrame = true;
    window.requestAnimationFrame(() => {
        scheduledAnimationFrame = false;
        // do something
    });
};

固然,最好的运用场景照样在帧动画里,能够大幅优化机能。

面试题

怎样衬着几万条数据并不卡住界面

这道题考核了怎样在不卡住页面的情况下衬着数据,也就是说不能一次性将几万条都衬着出来,而应当一次衬着部份 DOM,那末就能够经由过程 requestAnimationFrame 来每 16 ms 革新一次。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <ul>控件</ul>
  <script>
    setTimeout(() => {
      // 插进去十万条数据
      const total = 100000
      // 一次插进去 20 条,假如以为机能不好就削减
      const once = 20
      // 衬着数据统共须要频频
      const loopCount = total / once
      let countOfRender = 0
      let ul = document.querySelector("ul");
      function add() {
        // 优化机能,插进去不会形成回流
        const fragment = document.createDocumentFragment();
        for (let i = 0; i < once; i++) {
          const li = document.createElement("li");
          li.innerText = Math.floor(Math.random() * total);
          fragment.appendChild(li);
        }
        ul.appendChild(fragment);
        countOfRender += 1;
        loop();
      }
      function loop() {
        if (countOfRender < loopCount) {
          window.requestAnimationFrame(add);
        }
      }
      loop();
    }, 0);
  </script>
</body>
</html>

兼容性

一些老的浏览器是不支持这个api的,为了也能运用这个api, 能够自定义这个要领,挂载到window下面:

(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'] || window[vendors[x]+'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (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);
        };
}());
    原文作者:hudyseu
    原文地址: https://segmentfault.com/a/1190000017332455
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞