# requestAnimationFrame 动画帧

rAF是requestAnimationFrame的简称;

window.requestAnimationFrame() 告诉浏览器——希望执行一个动画,并且要求在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

注意:若想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame()

window.requestAnimationFrame(callback);
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
    <style media="screen">
    body {background:black; text-align: center;}
    #c1 {background:#FFF}
    </style>
    <script>
    window.onload=function (){
      let oC=document.getElementById('c1');
      let gd=oC.getContext('2d');
      let left=100;

      requestAnimationFrame(next);

      function next(){
        gd.clearRect(0,0,oC.width,oC.height);

        left+=0.2;

        gd.strokeRect(left, 100, 200, 150);

        //
        requestAnimationFrame(next);
      }
    };
    </script>
  </head>
  <body>
    <canvas id="c1" width="800" height="600"></canvas>
  </body>
</html>

# setTimeout

如果前面的代码影响了性能,就会导致 setTimeout 不会按期执行。可以通过代码去修正 setTimeout,从而使定时器相对准确

let period = 60 * 1000 * 60 * 2
let startTime = new Date().getTime()
let count = 0
let end = new Date().getTime() + period
let interval = 1000
let currentInterval = interval

function loop() {
  count++
  // 代码执行所消耗的时间
  let offset = new Date().getTime() - (startTime + count * interval);
  let diff = end - new Date().getTime()
  let h = Math.floor(diff / (60 * 1000 * 60))
  let hdiff = diff % (60 * 1000 * 60)
  let m = Math.floor(hdiff / (60 * 1000))
  let mdiff = hdiff % (60 * 1000)
  let s = mdiff / (1000)
  let sCeil = Math.ceil(s)
  let sFloor = Math.floor(s)
  // 得到下一次循环所消耗的时间
  currentInterval = interval - offset 
  console.log('时:'+h, '分:'+m, '毫秒:'+s, '秒向上取整:'+sCeil, '代码执行时间:'+offset, '下次循环间隔'+currentInterval) 

  setTimeout(loop, currentInterval)
}

setTimeout(loop, currentInterval)

# window.requestAnimationFrame()

取消一个先前通过调用window.requestAnimationFrame()方法添加到计划中的动画帧请求.

用动画帧优化替代setInterval

function setInterval(callback, interval) {
  let timer
  const now = Date.now
  let startTime = now()
  let endTime = startTime
  const loop = () => {
    timer = window.requestAnimationFrame(loop)
    endTime = now()
    if (endTime - startTime >= interval) {
      startTime = endTime = now()
      callback(timer)
    }
  }
  timer = window.requestAnimationFrame(loop)
  return timer
}

let a = 0
setInterval(timer => {
  console.log(1)
  a++
  if (a === 3) cancelAnimationFrame(timer)
}, 1000)
  • requestAnimationFrame调用的函数还有一个参数,可以自动记录时间,根据需求可以进行一些判断和拦截。
<div id="div" style="width:100px; height:100px; background-color:#000; position: absolute;left:0; top:0;"> 
</div>
<script type="text/javascript">
let divEle = document.getElementById("div");
const distance = 1500; // 需要移动的距离
const timeCount = 3000; // 需要使用的时间
function handler( time ) {
    // time为rAF返回的毫秒级时间单位,当time的大于timeCount的值则停止
    // time理论上是从 1 开始到timeCount定义的3000,
    if(time > timeCount) {
        time = timeCount;
    }
    // 这句代码的作用是 time理论上是从 1 至 3000
    // 当到达3000的时候,time * distance / timeCount得到的一定是distance的值1500
    divEle.style.left = time * distance / timeCount;
    window.requestAnimationFrame( handler ); // 循环调用,渲染完成会停止
}
 window.requestAnimationFrame( handler );
</script>

# setTimeOut和setInterval的缺点

  • 「造成无用的函数运行开销:」

也就是过度绘制,同时因为更新图像的频率和屏幕的刷新重绘制步调不一致,会产生丢帧,在低性能的显示器动画看起来就会卡顿。

  • 「当网页标签或浏览器置于后台不可见时,仍然会执行,造成资源浪费」

  • 「API本身达不到毫秒级的精确:」

如果使用 setTimeout或者setInterval 那么需要我们制定时间 假设给予 (1000/60)理论上就可以完成60帧速率的动画。所以事实是浏览器可以“强制规定时间间隔的下限(clamping th timeout interval)”,一般浏览器所允许的时间再5-10毫秒,也就是说即使你给了某个小于10的数,可能也要等待10毫秒。

  • 「浏览器不能完美执行:」 当动画使用10ms的settimeout绘制动画时,您将看到一个时序不匹配,如下所示。

这种透支会导致动画断断续续,「因为每三帧都会丢失」。计时器分辨率的降低也会对电池寿命产生负面影响,并降低其他应用程序的性能。

最后更新: 4/6/2022, 7:07:35 AM