requestAnimationFrame

requestAnimationFrame 是浏览器专门为动画提供的 API,它的刷新频率与显示器的频率保持一致,使用该 api 可以解决用 setTimeout/setInterval 制作动画卡顿的情况。

很长时间以来,计时器和定时执行都是 JavaScript 动画最常用的工具。虽然 CSS 过渡和动画方便了Web开发者实现某些动画,但 JavaScript 动画领域多年来进展甚微。Firefox 4 率先在浏览器中为 JavaScript 动画增加了一个名为 mozRequestAnimationFrame() 方法的API。这个方法会告诉浏览器要执行动画了,于是浏览器可以 通过最优方式确定重绘的时序。自从出现之后,这个API被广泛采 用,现在作为 requestAnimationFrame() 方法已经得到各大浏 览器的支持。

1)引擎层面

setTimeout/setInterval 属于 JS引擎,requestAnimationFrame 属于 GUI引擎

JS引擎与GUI引擎是互斥的,也就是说 GUI 引擎在渲染时会阻塞 JS 引擎的计算

2)时间是否准确

requestAnimationFrame 刷新频率是固定且准确的,但 setTimeout/setInterval 是宏任务,根据事件轮询机制,其他任务会阻塞或延迟 js 任务的执行,会出现定时器不准的情况

3)性能层面

当页面被隐藏或最小化时,setTimeout/setInterval 定时器仍会在后台执行动画任务,而使用 requestAnimationFrame 当页面处于未激活的状态下,屏幕刷新任务会被系统暂停

早期定时动画

以前,在JavaScript中创建动画基本上就是使用 setInterval() 来控制动画的执行。

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

setInterval() 和 setTimeout() 的不精确是个大问题。

requestAnimationFrame

requestAnimationFrame() 方法接收一个参数,此参数是 一个要在重绘屏幕前调用的函数。这个函数就是修改DOM样式以反映 下一次重绘有什么变化的地方。为了实现动画循环,可以把多个 requestAnimationFrame() 调用串联起来,就像以前使用 setTimeout() 时一样

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1">
  7. <title>requestAnimationFrame</title>
  8. <style>
  9. body {
  10. width: 98%;
  11. text-align: center;
  12. }
  13. #status {
  14. background-color: #1890ff;
  15. height: 20px;
  16. border-radius: 1000px;
  17. }
  18. </style>
  19. </head>
  20. <body>
  21. <div id="status" style="width:0;background-color: #1890ff;border-radius: 1000px;">
  22. </div>
  23. <button onclick="fun()">request</button>
  24. </body>
  25. <script>
  26. function updateProgress() {
  27. var div = document.getElementById("status");
  28. console.log('div.style.width ==', div.style.width);
  29. div.style.width = (parseInt(div.style.width) + 1) + "%";
  30. if (parseInt(div.style.width) <= 100) {
  31. requestAnimationFrame(updateProgress);
  32. }
  33. }
  34. function fun() {
  35. console.log('fun')
  36. requestAnimationFrame(updateProgress);
  37. }
  38. </script>
  39. </html>

cancelAnimationFrame

与 setTimeout() 类似, requestAnimationFrame() 也 返回一个请求ID,可以用于通过另一个方法 cancelAnimationFrame() 来取消重绘任务。

  1. let requestID = window.requestAnimationFrame(()=> {
  2. console.log('Repaint!');
  3. });
  4. window.cancelAnimationFrame(requestID);