做有态度的前端团队

网易FEG前端团队

用requestAnimationFrame来代替setInterval

用requestAnimationFrame来代替setInterval

在高级浏览器中,我们用requestAnimationFrame这个方法来代替setInterval和setTimout来做定时器。


requestAnimationFrame的执行机制

目前的显示器大部分的PSF都是60帧/秒,即每1000/60 = 16.7ms刷新一次,如果setTimeout/setInterval设置的时间间隔小于16.7,那么就会出现过度绘制的问题。而requestAnimationFrame正是为了这个而出现的,它会跟着浏览器的绘制走,浏览器每次重绘会通知requestAnimationFrame:我要重绘了。所以如果浏览器的绘制间隔是16.7s,它就会每隔16.7s绘制一次,这就不存在过度绘制导致掉帧的问题。

  • 而且就算有多个requestAnimationFrame存在,浏览器会统一通知,而setTimeout是相互独立的,所以会耗费更多资源。
  • 当页面tab切换到其他页面时,requestAnimationFrame会停止运行,节约资源。(chrome浏览器对时间间隔小于1s的setInterval也)

过度绘制

其实也没有这样一个专业术语,找组长详细了解了一下浏览器刷新的细节以及根据自己的理解总结出来的这么一个概念。
人眼能分辨出的最高的显示器刷新频率是60帧,再高的频率人眼也看不出来了,所以如果你的动画时间间隔太小,那么中间的一些帧人眼就看不到,所以会看起来好像掉了一些帧。

使用方法

可以直接调用,也可以通过window来调用,接收一个函数作为回调,返回一个ID值,通过把这个ID值传给window.cancelAnimationFrame()可以取消该次动画。

    var draw= function(){
        drawPic();
        requestAnimationFrame(draw);
    }
    draw();

兼容代码

为了兼容IE低版本,以下代码段是兼容写法,低版本IE下使用setTimeout代替执行:

(function() {
  var lastTime = 0;
  var version = ['webkit', 'moz'];
  for(var i = 0; i < version.length && !window.requestAnimationFrame; i++) {   
        //如果此浏览器不支持requestAnimationFrame方法,就循环遍历version数组
    window.requestAnimationFrame = window[version[i] + 'RequestAnimationFrame'];
    window.cancelAnimationFrame = window[version [i] + 'CancelAnimationFrame'] ||         
        // 有一些Webkit版本中,此方法的名字改变了
      window[version [i] + 'CancelRequestAnimationFrame'];
  }

  if (!window.requestAnimationFrame) {   //如果是IE9-浏览器
    window.requestAnimationFrame = function(callback, element) {      
    //当我们点击div时,就会触发start方法,我们假设当前时间为11111,设置startTime=11111, 
        //调用requestAnimationFrame(animate)方法,这时,当前时间,我们假设是currTime = 11112,
        //lastTime = 0,这时timeToCall = 0,因此调用setTimeout(function(){},0),
        //把lastTime = 11111,返回id。过了浏览器的最小时间后,我们假设是4ms,
        //就会立即执行animate(11112)。这时就会继续执行requestAnimationFrame。
        //假设当前时间是11118,timeToCall = 9.7,这时lastTime = 11127.7,
        //当前时间为11127.7时,就执行animate(11127.7),per = 16.7 / 3000,继续执行requestAnimationFrame....
      var currTime = new Date().getTime();
      var timeToCall = Math.max(0, 16.7 - (currTime - lastTime));  
         //timeToCall的值为0-16.7之间。

      var id = window.setTimeout(function() {
        callback(currTime + timeToCall);
      }, timeToCall);
      lastTime = currTime + timeToCall;
      return id;
    };
  }
  if (!window.cancelAnimationFrame) {   
    window.cancelAnimationFrame = function(id) {
      clearTimeout(id);
    };
  }
})();

手机阅读请扫描下方二维码:

添加新评论

ali-40.gifali-41.gifali-42.gifali-43.gifali-44.gifali-45.gifali-46.gifali-47.gifali-48.gifali-49.gifali-50.gifali-51.gifali-52.gifali-53.gifali-54.gifali-55.gifali-56.gifali-57.gifali-58.gifali-59.gifali-60.gifali-61.gif