面试官:说说 requestAnimationFrame

149 阅读2分钟

requestAnimationFrame

简介

requestAnimationFrame 是 HTM5 新增的一个定时器,它可以指定一个回调函数,要求浏览器在下次重绘之前调用指定的回调函数更新动画,并且时间间隔为一帧,能解决普通定时器时间间隔不稳定的问题

可能看了简介之后还是不明白说了啥,在讲 requestAnimationFrame 之前先讲定时器

定时器

很多人可能会问:为什么不直接使用 setTimeOut() 和 setInterval() 来做动画呢?

首先,大多数浏览器屏幕刷新的时间间隔为 60Hz 即 1000/60 ms,那么 requestAnimationFrame 每隔16.67ms执行一次;如果屏幕刷新的时间间隔为 75Hz 即 1000/75 ms,那么 requestAnimationFrame 每隔13.33ms执行一次。这个时间间隔我们称为一帧
然而:
(1)普通定时器的时间参数可任意指定,无论这个时间是大于一帧还是小于一帧,都跟页面的刷新频率不同步,体验感不佳。
(2)由于 setTimeOut()setInterval() 是异步任务,定时器创建之后其回调函数会被放到异步队列当中去,当主线程任务执行完毕之后,浏览器才会去任务队列中检查是否有任务需要被执行,如果有就从队列当中取出并执行。因此,定时器设定的间隔时间 t 的含义其实是:间隔 t ms将回调函数放进异步任务队列,而不是间隔 t ms执行回调函数。综上所述,普通定时器的回调函数的延迟执行时间会比设定的时间晚。即使把时间调到了 16.67ms(以大部分浏览器的刷新频率为例)的情况,也不会刚好是 16.67ms 后执行回调函数。

那使用 requestAnimationFrame 有什么好处呢

(1)requestAnimationFrame 不能设置时间参数,其时间间隔不由JS控制,而是由系统时间间隔决定。如果屏幕刷新频率为16.67ms,则 requestAnimationFrame 每隔16.67ms立即执行一次;如果屏幕刷新频率为13.33ms,则其每隔13.33ms立即执行一次;
(2)由于使用了 requestAnimationFrame 后,其指定的回调函数会在浏览器下次重绘之前被调用,而requestAnimationFrame 的时间间隔又刚好为一帧,因此能保证回调函数在屏幕的每次刷新间隔中只被执行一次。

使用方法

(1)跟普通的定时器基本一致;
(2)若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame()。

cancelAnimationFrame()

(1)作用是取消 requestAnimationFrame,相当于普通定时器当中的 clearTimeout(),clearInterval()。
(2)requestAnimationFrame 使用后会返回一个id,该id传递给 cancelAnimationFrame() 即可清除。

用法案例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div class="testBox"></div>
  </body>
  <script>
    const testBox = document.querySelector(".testBox");
    testBox.style.width = "15px";
    testBox.style.height = "20px";
    testBox.style.backgroundColor = "pink";
    testBox.onclick = () => {
      let timer = requestAnimationFrame(function fn() {
        if (parseInt(testBox.style.width) < 300) {
          testBox.style.width = parseInt(testBox.style.width) + 3 + "px";
          testBox.innerHTML = parseInt(testBox.style.width) / 3 + "%";
          timer = requestAnimationFrame(fn);
        } else {
          cancelAnimationFrame(timer);
        }
      });
    };
  </script>
</html>