js帧动画之window.requestAnimationFrame

文章发布于 2023-05-11

什么帧动画?

显示器都有一个刷新率。一般的显示器刷新率都是60Hz。相当于浏览器在每秒可以重绘60次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过了这个频率,用户体验也不会有提升。所以平滑动画的最佳循环间隔是1s/60,约等于17ms。这个循环间隔重绘的动画是平滑的,因为这个速度最接近浏览器的最高限速。为了适应17ms的循环间隔,多重动画可能需要加以节制,以便不会完成得太快。

setIntervel 和setTimeout 的不足

虽然使用setInterval比setTimeout相率更高,但是两种方法都不够精准。他们都无法去自适应浏览器的重绘。

自适应帧动画window.requestAnimationFrame

window.requestAnimationFrame是自适应浏览器的重绘,速度是由浏览器的帧决定的,不同浏览器自行决定最佳的帧效率。

<!DOCTYPE html>
<html>
<head>
    <title>window.requestFrame</title>
</head>
<body>
    <div style="border: 1px ; 200px;height: 200px;background-color: pink;position: absolute;" id="dv">

    </div>
    <script type="text/javascript">
        var divPosition = 0;
        var div = document.getElementById('dv');
        window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
        div_width = parseInt(div.style.width);
        function step(timestamp) {
            divPosition+=10;
            div.style.left = divPosition + 'px';
            if(divPosition < window.screen.availWidth - div_width){
                requestAnimationFrame(step);
            }
        }
        requestAnimationFrame(step);
    </script>
</body>
</html>

兼容写法:

if(!window.requestAnimationFrame) {
 window.requestAnimationFrame = (window.webkitRequestAnimationFrame ||
 window.mozRequestAnimationFrame ||
 window.oRequestAnimationFrame ||
 window.msRequestAnimationFrame ||
 function(callback) {
  var self = this, start, finish;
  return window.setTimeout(function() {
   start = +new Date();
   callback(start);
   finish = +new Date();
   self.timeout = 1000/60 - (finish - start);
  }, self.timeout);
 });
}

封装window.requestAnimationFrame

(function() {
 var lastTime = 0;
 var vendors = ['ms', 'moz', 'webkit', 'o'];
 for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
  window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
  window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
 }
 
 if (!window.requestAnimationFrame)
  window.requestAnimationFrame = function(callback, element) {
   var currTime = new Date().getTime();
   var timeToCall = Math.max(0, 16 - (currTime - lastTime));
   var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 
    timeToCall);
   lastTime = currTime + timeToCall;
   return id;
  };
 
 if (!window.cancelAnimationFrame)
  window.cancelAnimationFrame = function(id) {
   clearTimeout(id);
  };
}());

实例

通过window.requestAnimationFrame实现平滑的返回顶部。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>返回顶部</title>
  <style>
    .box {
      margin: 0 auto;
      width: 100%;
      height: 5000px;
    }
 
    .box1 {
      background: #b94a48;
    }
 
    .box2 {
      background: #fb8c00;
    }
 
    .box3 {
      background: #669900;
    }
 
    .box4 {
      background: #c0a16b;
    }
 
    .top {
      position: fixed;
      right: 20px;
      bottom: 20px;
      width: 40px;
      height: 40px;
      background: #8dc7ff;
      border-radius: 50%;
      cursor: pointer;
    }
  </style>
  <script>
    window.requestAnimationFrame = (function () {
      return window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        function (callback) {
          window.setTimeout(callback, 6000 / 60)
        }
    })()
 
    window.cancelAnimFrame = (function () {
      return window.cancelAnimationFrame ||
        window.webkitCancelAnimationFrame ||
        window.mozCancelAnimationFrame ||
        window.oCancelAnimationFrame ||
        window.msCancelAnimationFrame ||
        function (callback) {
          window.clearTimeout(callback)
        }
    })()
 
    function scrollToTop() {
      let top = window.pageYOffset
      const duration = 320
      const step = top / (duration / (1000 / 60)) >> 0
      const fn = () => {
        if (top >= 0) {
          top -= step
          window.scrollTo(0, top)
          fn.rafTimer = window.requestAnimationFrame(fn)
        } else {
          window.scrollTo(0, 0)
          window.cancelAnimationFrame(fn.rafTimer)
        }
      }
      fn.rafTimer = window.requestAnimationFrame(fn)
    }
  </script>
</head>
<body>
<div class="box box1"></div>
<div class="box box2"></div>
<div class="box box3"></div>
<div class="box box4"></div>
<div class="top" onclick="scrollToTop()"></div>
</body>
</html>