requestAnimationFrame && setTimeout 原理剖析

就目前来讲,web应用实现动画的方式有很多种,排除CSS3自带的transitionanimation动画属性,这里讲一下JavaScript实现动画的其中两种方法,requestAnimationFrame && setTimeout

  • 前言

在开始分析之前,我们要了解几个概念,视觉暂留 屏幕刷新频率

1.视觉暂留

  眼睛的另一个重要特是视觉惰,即光象一旦在视网膜上形成,视觉将会对这个光象的感觉维持一个有限的时间,这种生理现象叫做视觉暂留。对于中等亮度的光刺激,视觉暂留时间约为50ms200ms。当我们看屏幕的时候,虽然你什么也没做,但是屏幕还是以特定的频率在不停刷新,只是这个刷新过程我们肉眼识别到他的细微变化,这就是我们接下来要说的 屏幕刷新频率

2.屏幕刷新频率

  我们日常的显示器,一般频率在60Hz左右,意味着我们的屏幕每1秒需要刷新60次,也就是说每1000ms需要更新60次的屏幕图像,那么我们由此可以得出,屏幕图像更新一次所需要的时间间隔也就是16.7ms(1000/60≈16.7)
  由于人的眼睛具有视觉暂留效应,且暂留时间为50ms200ms,也就是说人在看屏幕的时候,还没等到你的大脑印象消失,电脑屏幕就已经更新了,所以这个间隔让你感觉不到变化。
  那么屏幕刷新频率是不是越大越好?我们可以大胆假设一下,假如我有三个显示器,刷新频率分别为1Hz60Hz200Hz、那么对应的更新周期时间分别为1000ms16.7ms5ms也就是频率越大,图像更新的间隔就越短,我们看到的画面就会越稳定,当达到一秒更新一次的时候,这个时候我们就能够感觉到明显的屏幕闪烁,带来视觉疲劳。

requestAnimationFrame && setTimeout 原理剖析_第1张图片
显示器配置信息

3.setTimeout

  setTimeout说白了就是个延时计时器,通过设置固定的时间间隔,从而时间一到,执行相应的回调方法。这个过程不会考虑屏幕刷新频率,换句话讲,它的时间是写死的。那么为什么会存在丢帧现象发生,通俗来说就是为什么使用setTimeout我会感觉到卡顿,画面不稳定。

  1.setTimeout执行的时间与屏幕的刷新频率不一致会导致丢帧现象。我们不考虑异步问题,假设我们现在的屏幕设备是60Hz的刷新频率。那么我们图像的更新周期也就是16.7ms,我现在的动画要求是每10ms往下偏移1px,那么这个丢帧现象是如何产生的?这里我通过一张图来解释一下。

requestAnimationFrame && setTimeout 原理剖析_第2张图片
分析图

分析结果:
  • 第1次重绘(16.7ms):图形偏移到1px
  • 第2次重绘(33.4ms):图形偏移到3px丢失1px偏移;
  • 第3次重绘(50.1ms):图形偏移到4px
  • 第4次重绘(66.8ms):图形偏移到6px丢失1px偏移;
  • 第5次重绘(83.5ms):图形偏移到8px丢失1px偏移;
    ......
实验效果:
实验效果

  所以根据分析结果以及实验效果,如果setTimeout执行的顺序与屏幕的刷新频率不一致,会造成丢帧现象,从而视觉上带给我们的就是不流畅

  2.由于JavaScript属于单线程,而setTimeout任务会被放入异步队列,通俗来讲就是它的执行得等一等,具体等什么,不知道,就是想再等等。只有主线程的任务执行完毕之后,才会轮到它去执行,也就是说我虽然设置setTimeout 16.7ms间隔去执行动画属性改变,但是实际运行的时间可能会有所延迟。这个延迟可能会导致执行的时间与屏幕刷新的时间串掉,造成丢帧。

requestAnimationFrame && setTimeout 原理剖析_第3张图片
分析图

分析结果:
  • 第1次重绘(16.7ms):图形未偏移;应该偏移到1px
  • 第2次重绘(33.4ms):图形偏移到1px应该偏移到2px
  • 第3次重绘(50.1ms):图形偏移到2px;应该偏移到3px
  • 第4次重绘(66.8ms):图形偏移到3px应该偏移到4px
  • 第5次重绘(83.5ms):图形偏移到4px应该偏移到5px
    ......
      所以根据分析结果,我们可以看出,在异步的现象下,会造成一连串的执行差,从而造成丢帧现象

4.requestAnimationFrame

  官方解释window.requestAnimationFrame()告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。也就是说requestAnimationFrame它不需要你去手动设置执行间隔时间,它是跟随系统的屏幕刷新频率走的,如果屏幕刷新频率是60Hz,那么它的执行间隔就是16.7ms(1000/60≈16.7),如果屏幕刷新频率是100Hz,那么它的执行间隔就是10ms(1000/100=10),这样就能够保证它的执行与屏幕的刷新频率保持一致,从而避免丢帧现象。
  为了提高性能和电池寿命,因此在大多数浏览器里,当requestAnimationFrame() 运行在后台标签页或者隐藏的