情诗网 >心情说说 > 正文

说说requestAnimationFrame

来源:情诗网    2020-11-04    分类:心情说说

首先从字面意思来看,就是请求动画帧(后面用raf代替)。
说起动画,css3的transition和animation,js的setTimeout和setInterval,jquery的animate()都可以做动画,这几种方式的性能和效率也有不同。今天聊的这个也可以做动画。

是什么

说白了就是做帧动画用的,所谓的请求动画帧,是告诉浏览器在下一次屏幕刷新的时候,别忘了运行raf这个方法,这一块要注意一点:每次屏幕刷新的时候,都会运行raf。也就是raf的运行频率是根据屏幕刷新频率来决定的。

性能怎么样

首先聊聊setTimeout。
setTimeout 其实就是通过设置一个间隔时间来不断的改变图像的位置,从而达到动画效果的。但我们会发现,利用seTimeout实现的动画在某些低端机上会出现卡顿、抖动的现象。 这种现象的产生有两个原因:

1.setTimeout的执行时间并不是确定的。在Javascript中, setTimeout 任务被放进了异步队列中,实际上只是指定了把动画代码添加到浏览器UI线程等待执行的时间,如果队列前面有其他任务,只有当主线程上的任务执行完以后,才会去检查该队列里的任务是否需要开始执行,因此 setTimeout 的实际执行时间一般要比其设定的时间晚一些。
2.刷新频率受屏幕分辨率和屏幕尺寸的影响,因此不同设备的屏幕刷新频率可能会不同,而 setTimeout只能设置一个固定的时间间隔,这个时间不一定和屏幕的刷新时间相同。
以上两种情况都会导致setTimeout的执行步调和屏幕的刷新步调不一致,从而引起丢帧现象。 那为什么步调不一致就会引起丢帧呢?

首先要明白,setTimeout的执行只是在内存中对图像属性进行改变,这个变化必须要等到屏幕下次刷新时才会被更新到屏幕上。如果两者的步调不一致,就可能会导致中间某一帧的操作被跨越过去,而直接更新下一帧的图像。假设屏幕每隔16.7ms刷新一次,而setTimeout每隔10ms设置图像向左移动1px, 就会出现如下绘制过程:

第0ms: 屏幕未刷新,等待中,setTimeout也未执行,等待中;
第10ms: 屏幕未刷新,等待中,setTimeout开始执行并设置图像属性left=1px;
第16.7ms: 屏幕开始刷新,屏幕上的图像向左移动了1px, setTimeout 未执行,继续等待中;
第20ms: 屏幕未刷新,等待中,setTimeout开始执行并设置left=2px;
第30ms: 屏幕未刷新,等待中,setTimeout开始执行并设置left=3px;
第33.4ms:屏幕开始刷新,屏幕上的图像向左移动了3px, setTimeout未执行,继续等待中;

从上面的绘制过程中可以看出,屏幕没有更新left=2px的那一帧画面,图像直接从1px的位置跳到了3px的的位置,这就是丢帧现象,这种现象就会引起动画卡顿。所以丢帧现象可以说是因为屏幕刷新频率和代码执行不对称导致的。

那如果屏幕刷新频率和代码执行对称的话,是不是就不会发生丢帧现象了??。raf的诞生就解决了这一点,上文已经说了,raf的执行频率是根据屏幕刷新频率来说的,有图像的变化就会刷新屏幕,刷新屏幕就会运行raf,这样就解决了丢帧的现象。

如何用

raf()会接收一个参数,这个参数是一个函数,函数的作用就是负责改变下一次重绘时dom的样式。

var i = 0;
function updateDom(){
  let box = document.getElementById('box');
  i+=10;
  box.style.width = 300 + i +'px';
  if(i<500){
      requestAnimationFrame(updateDom)
  }
}
requestAnimationFrame(updateDom)

到此,raf解决了动画什么时候开始和最佳循环循环间隔的问题,但是代码具体的在哪个时间执行吗,确是不知道的,只知道动画在屏幕刷新时候开始。不过,mozRequestAnimationFrame()传递的函数也会接收一个参数,是一个时间码(从1970年1月1日起至今的毫秒数),表示下一次重绘发生的实际时间。
如果想知道距离上一次重绘过去的时间,可以查询mozAnimationStartTime,不过这个属性知识Mozilla实现了。目前chrome和IE都没有此属性。这个值包含上一次重绘的时间码,用传入回调函数的时间码减去这个时间,就能计算出在屏幕上重绘下一簇变化之前要经过多少时间。

function draw(timestamp){
  var differ = timestamp - startTime;//differ确定下一次重绘时间与现在时间的差值
  startTime = timestamp;//把timestamp重写为这一次的绘制时间
  mozRequestAnimationFrame(draw);
}
var startTime = mozAnimationStartTime;
mozRequestAnimationFrame(draw)

chrome和IE10+都给出了带前缀的requestAnimationFrame方法实现,不过与Mozilla的版本还是有点差异。
差异1:不回给回调函数传时间码。
差异2:chrome增加了第二个可选的参数:即将要发生变化的DOM元素。
chrome提供了一个取消帧动画的方法,webkitCancelAnimationFrame(),用于取消之前计划的重绘操作。
当然,作为一个新兴的API,兼容性问题肯定少不了。所以实际开发过程中,请注意兼容性的处理。

requestAnimationFrame的优点:
1.多个raf可以并行绘制。(这一点跟setTimeout和setInterval进入异步队列有关系。)
2.在隐藏或者不可见的元素汇总,raf将不会进行重绘和回流,这就意味着更少的CPU和GPU以及内存使用量。

  1. 由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态的话,动画就会暂停,节省了CPU开销。(对于这一点,本人试了试setTimeout和setInterval,发现用这两者做的动画在不激活的状态下也会暂停,不知道是chrome浏览器做了优化还是说只有chrome是这样。)

4.函数节流: 在高频率时间中(resize,scroll等),为了防止在一个刷新间隔内发生多次函数执行,使用raf可保证每个刷新间隔内,函数只能被执行一次,这样既能保证流浪性,也能更好的节省函数执行的开销。

热门文章