早期的定时动画

以 setInterval() 来控制动画执行

  1. ;(function(){
  2. function updateAnimations(){
  3. doAnimation1();
  4. doAnimation2();
  5. // 其它任务
  6. }
  7. setInterval(updateAnimations, 100);
  8. })();

作为一个小型动画库的标配,这个 updateAnimations() 方法会周期性运行注册的动画任务,并反映出每个任务的变化。如果没有动画需要更新,则这个方法即可以什么不做直接退出,也可以停止动画循环,等待其它需要更新的动

缺点

问题在于无法准确知晓循环之间的延时。

  • 定时间隔必须足够短,才能让不同的动画类型都能平滑顺畅,但又要足够长,以便浏览器可以渲染出来的变化。
  • 实现平滑动画最佳重绘间隔为1000毫秒 / 60,大约 17 毫秒。

不能保证时间精度

  • 第二个参数的延时只能保证何时会把代码添加动任务队列,但不能保证添加到队列就会立即执行。
    • 队列前面还有其它任务,要等待前置任务完成后才执行。

      时间间隔

      IE8 以前 15.625 ms
      IE9 以后 4 ms
      Firefox / Safari 10 ms
      Chrome 4 ms

      requestAnimationFrame

      Firefox 4 在浏览器中为 JavaScript 动画增加一个名为 mozRquestAnimationFrame() 的 API。这个方法会告诉浏览器要执行动画,浏览器会以最优时序确定重绘。
      之后,这个 API 被广泛使用,现在作为 requestAnimationFrame() 方法已经得到各大浏览器的支持。

参数

  • 重绘屏幕前调用的函数

点击查看【codepen】
requestAnimationFrame() 只会调用一次传入的函数,每次更新用户界面时需要再手动调用它一次。
传给 requestAnimationFrame() 的函数可以接收一个参数

  • DOMHighResTimeStamp 实例,performance.now() 表示下次重绘的时间
  • 实际上把重绘任务安排在未来一个书籍的时间点上

    cancelAnimationFrame

    类似于 clearInterval()
    通过 requestAnimationFrame() 返回的请求 ID,以 cancelAnimationFrame 取消重绘任务。

通过 requestAnimationFrame 节流

这个方法实际上会在浏览器执行下一次重绘之前的一个点,调用函数。每次调用 requestAnimationFrame() 都会在生重绘队列上推入一个回调函数,队列的长度没有限制。

定义一个标志变量,由回调设置其开关状态,就可以将多余的调用屏蔽

let enqueued = false; 
function expensiveOperation() { 
  console.log('Invoked at', Date.now()); 
  enqueued = false; 
} 
window.addEventListener('scroll', () => { 
  if (!enqueued) { 
    enqueued = true; 
    window.requestAnimationFrame(expensiveOperation); 
  } 
});

因为重绘是非常频繁的操作,所以这还算不上真正的节流。更好的办法是配合使用一个计时器来限制操作执行的频率。这样,计时器可以限制实际的操作执行间隔,而 requestAnimationFrame 控制在浏览器的哪个渲染周期中执行。下面的例子可以将回调限制为不超过 50 毫秒执行一次

let enabled = true; 
function expensiveOperation() { 
  console.log('Invoked at', Date.now()); 
} 
window.addEventListener('scroll', () => { 
  if (enabled) { 
    enabled = false; 
    window.requestAnimationFrame(expensiveOperation); 
    window.setTimeout(() => enabled = true, 50); 
  } 
});