requestAnimationFrame 持续动画效果

1. requestAnimationFrame 概述

requestAnimationFrame 是浏览器用于定时循环操作的一个API, 类似于setTimeout, 主要用途是按帧对网页进行重绘.

设置这个API的目的就是为了让各种网页动画效果 (DOM动画, Canvas动画, SVG动画, WebGL动画) 能有一个统一的刷新机制, 从而节省系统资源, 提高系统性能, 改善视觉效果. 使用这个API, 就是告诉浏览器希望执行一个动画, 让浏览器在下一个动画帧对网页进行一次网页重绘.

requestAnimationFrame 的优势在于, 充分利用了显示器的刷新机制, 比较节省系统的资源, 显示器的固定刷新频率是 60HZ或者75HZ, 也就是说, 每秒最多能绘制60次或者75次, 这个API的思想就是与这个刷新频率保持同步, 利用这个刷新频率对网页进行重绘. 另外, 使用这个API, 当浏览器不处于当前标签页, 就会自动停止刷新.

缺点是, requestAniamtionFrame 是在主线程上完成的, 这就意味着, 一旦主线程非常繁忙, requestAnimationFrame的动画效果会大打折扣.

requestAnimationFrame 使用一个回调函数作为参数, 这个回调函数在流浪器重绘之前调用

var requestID = window.requestAnimationFrame(calback)

目前主要浏览器都支持这个API, 查看更多兼容性 

即使主流浏览器都支持, 但还是要检查浏览器是否支持, 而且各浏览器都带有前缀, 如果不支持, 就使用setTimeout模拟该方法

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

上面的代码按照1秒60次来模拟requestAnimationFrame, 使用的时候只需要反复调用即可

function repeatOften() {
  // Do whatever
  requestAnimationFrame(repeatOften);
}

requestAnimationFrame(repeatOften);

2. cancelAnimationFrame 方法

cancelAnimationFrame方法用于取消重绘

window.cancelAnimationFrame(requestID)

他的参数是requestAnimationFrame返回的一个代表任务ID的整数值

var globalID;

function repeatOften() {
  $("<div />").appendTo("body");
  globalID = requestAnimationFrame(repeatOften);
}

$("#start").on("click", function() {
  globalID = requestAnimationFrame(repeatOften);
});

$("#stop").on("click", function() {
  cancelAnimationFrame(globalID);
});

上面的代码就是不断的网body中添加div, 知道用户点击stop为止

3. 实例

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>request</title>
    <style type="text/css">
        #anim {
            height: 100px;
            width: 100px;
            border-radius: 6px;
            background-color: #f66;
            color: #fff;
            margin-top: 50px;
            position: absolute;
        }
    </style>
</head>
<body>
    <div id="anim">运动区域</div>
    <button id="start">开始</button>
    <button id="stop">停止</button>
</body>
<script type="text/javascript">
    window.requestAnimFrame = (function(){
        return  window.requestAnimationFrame || 
            window.webkitRequestAnimationFrame || 
            window.mozRequestAnimationFrame || 
            window.oRequestAnimationFrame || 
            window.msRequestAnimationFrame || 
            function(callback, element) {
                window.setTimeout(callback, 1000 / 60);
            };
    })();
    var anim = document.getElementById('anim');
    var start = document.getElementById('start');
    var stop = document.getElementById('stop');
    var startTime = undefined;
    var requestId = undefined;
    function render(time) {
        if (time === undefined) {
            time = Date.now();
        }
        if (startTime === undefined) {
            startTime = time;
        }
        anim.style.left = ((time - startTime) / 10 % 500) + 'px';
    }
    start.onclick = function() {
        (function animloop() {
            render();
            requestId = requestAnimationFrame(animloop, anim)
        })();
    }
    stop.onclick = function() {
        window.cancelAnimationFrame(requestId);
    }
</script>
</html>

在codepen看效果

下面看一个关于requestAnimationFrame的面试题

如何一次性加载几万条数据,要求不卡住界面?

道题考察了如何在不卡住页面的情况下渲染数据,也就是说不能一次性将几万条都渲染出来,而应该一次渲染部分 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 count = 0
      let requestId = 0
      let ul = document.querySelector("ul");
      function add() {
        // 优化性能,插入不会造成回流
        const fragment = document.createDocumentFragment();
        for (let i = 0; i < once; i++) {
          const li = document.createElement("li");
          count += 1
          li.innerText = Math.floor(count);
          fragment.appendChild(li);
        }
        ul.appendChild(fragment);
        loop();
      }
      function loop() {
        if (count < total) {
          requestId = window.requestAFrame(add);
        } else {
          window.cancelAFrame(requestId)
        }
      }
      loop();
    }, 0);
    // handle multiple browsers for requestAnimationFrame()
    window.requestAFrame = (function () {
        return window.requestAnimationFrame ||
                window.webkitRequestAnimationFrame ||
                window.mozRequestAnimationFrame ||
                window.oRequestAnimationFrame ||
                // if all else fails, use setTimeout
                function (callback) {
                    return window.setTimeout(callback, 1000 / 60); // shoot for 60 fps
                };
    })();

    // handle multiple browsers for cancelAnimationFrame()
    window.cancelAFrame = (function () {
        return window.cancelAnimationFrame ||
                window.webkitCancelAnimationFrame ||
                window.mozCancelAnimationFrame ||
                window.oCancelAnimationFrame ||
                function (id) {
                    window.clearTimeout(id);
                };
    })();
  </script>
</body>
</html>

参考链接

https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame

http://www.jb51.net/article/92994.htm

https://developer.mozilla.org/zh-CN/docs/Web/CSS/animation

http://www.zhangxinxu.com/wordpress/2013/09/css3-animation-requestanimationframe-tween-%E5%8A%A8%E7%94%BB%E7%AE%97%E6%B3%95/

原文地址:https://www.cnblogs.com/shenjp/p/8926521.html