Fork me on GitHub

requestAnimationFrame是什么?

对于web应用来说,实现动画的方式有很多。在CSS3的动画效果已经非常强大的前提下,我们一般是不推荐使用JS来实现动画效果的,一是因为JS动画通常性能上都非常不乐观,二是写起来也十分麻烦,而且不易维护。

但在某些场景下,当CSS3动画不能满足需求时,我们就不得不借助于JS来实现了。在以前我们一般使用setTimeout通过定时来控制元素的位置,角度,透明度,显隐等属性实现动画的过渡效果。

今天要介绍的主角requestAnimationFrame,通过名字也可以看出,它与动画有关(废话!!要不我上面扯半天动画干嘛)。那么requestAnimationFrame相比于setTimeout又有哪些突出的优势呢?

让我们通过这篇博客来一一解释。

动画

大家一定还记得,小时候我们看电影,放映员都是拿着一大盘的胶片,装在投影灯上来播放电影的,这也就是老式的胶片电影。通过将电影的一幕幕画面先洗在胶片上,然后将胶片快速的从投影灯前移动来打到大影幕上,从而在荧幕上显出连贯的画面来。胶片上的一个画面,就叫做一帧。

科技在发展,目前的显示器已经从老式的电子管到液晶,再到LED等材料,但不管显示器的科技如何更新,它基本的原理仍然是利用人眼的暂留效应,通过快速呈现出一帧帧静止的画面来实现基本的动态显示的。这个快速有多块呢?一般来说,普通显示器的显示频率是60Hz,也就是每16.7ms(1000/60)一帧。当然,现在有很多显示器早已达到了144HZ甚至更高。

再回到我们的正题,动画其实归根结底,也是更新每一帧的对应位置,从而形成一系列连贯平滑的屏幕显示图像,在人眼中表现出流畅的过渡效果。

实现动画的几种方式:

在网页中,我们实现动画主要有以下几种方式:

  • html5canvas
  • css3transitionanimation
  • JavaScriptsetTimeout

具体的来说,这几种方式都有相应的优势和局限性。

html5canvas

canvas的功能相当强大,可以实现很多酷炫的效果。但因为它本身是用来提供web的扩展支持和自主绘制的,除非在某些特定领域,大部分时间里对于我们平常需要动画效果来说,是不会用到它的。

css3的transition和animation

CSS3动画的优点在于实现简单,维护容易,可复用性高,同时也可以挖掘出很多相当强大的动画效果。所以一般的,我们都推荐使用CSS3来进行网页的动画开发。
但在某些场景下,使用CSS3是无法达到我们的需求的。典型的有以下两个场景:

  • 需要逻辑判断和复杂的前置条件处理的动画,例如与元素的scrollTop值相关的动画,例如根据不同条件进行不同表现的动画。

  • 非标准曲线的动画,因为CSS3只支持标准的贝塞尔曲线,所以它是无法实现很多复杂特殊的动画效果曲线的。

对于以上两种不适合CSS3的动画,我们通常会使用JS来实现。那下面我们就来看一下setTimeout的优缺点。

setTimeout

setTimeout通过设定一个时间间隔来不断的更新屏幕图像,从而完成动图。
它的优点是可控性高,可以进行编码式的动画效果实现。
但是有以下几个缺点:

  • 执行时间因为JS的线程和事件循环问题会不准确
  • 执行的时间间隔是固定的,在屏幕刷新的两帧间隔中无论setTimeout的回调函数执行了几次,都只会有最后一次有用,显示器只会更新最后一次执行结果对应的图像。
    造成无用的函数运行开销,也就是过度绘制,同时因为更新图像的频率和屏幕的刷新重绘制步调不一致,会产生丢帧,在低性能的显示器动画看起来就会卡顿。
  • 当网页标签或浏览器置于后台不可见时,仍然会执行,造成资源浪费。

requestAnimationFrame相比setTimeout,比较好的解决了以上的几个缺点。

requestAnimationFrame

翻译过来就是请求动画帧,简称rAF,它是浏览器全局对象window的一个方法。

相比于setTimeout的在固定时间后执行对应的动画函数,rAF用于指示浏览器在下一次重新绘制屏幕图像时, 执行其提供的回调函数。

这也是rAF的最大优势–它能够保证我们的动画函数的每一次调用都对应着一次屏幕重绘,从而避免setTimeout通过时间定义动画频率,与屏幕刷新频率不一致导致的丢帧。
它的另一个优点就是在页面被置于后台或隐藏时,会自动的停止,不进行函数的执行,当页面激活时,会重新从上次停止的状态开始执行,因此在性能开销上也会相比setTimeout小很多。
总的来说,requestAnimationFrame的优点就是:

  • 使浏览器画面的重绘和回流与显示器的刷新频率同步
  • 节省系统资源,提高性能和视觉效果

详细语法

它的基本用法类似于setTimeout如下:

JavaScript
1
2
3
4
5
6
7
8
9
10
let i = 0
const element = document.querySelector('#theEle')
function toLeftAnimation(){
element.style.left = ++i + 'px'
if (i < 1000) {
window.requestAnimationFrame(toLeftA)
}
}

window.requestAnimationFrame(toLeftA)

在每一次屏幕显示图像的更新中,都将元素向左移动1px
具体语法如下:

window.requestAnimationFrame(callback)

requestAnimationFrame方法接收一个函数参数callback,这个函数参数会在浏览器下次绘制前执行。
callback函数会被传入一个参数DOMHighResTimeStamp,这个参数的含义是指callback的执行时间,也就是下一次屏幕绘制发生的时间。
requestAnimationFrame方法返回一个长整数,作为本次方法调用的唯一标志ID,可以将这个ID传递给window.cancelAnimationFrame()来取消掉对应的回调函数。

兼容问题

目前的时间点上,几乎所有的浏览器现行版本都支持了rAF函数。但在一部分浏览器上还需要加上兼容性前缀。
下面是一个比较简单的兼容polyfill函数用来使requestAnimation兼容各浏览器:

1
2
3
4
5
6
7
8
window.requestAnimationFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequsetAnimationFrame ||
window.mozRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 16.7)
}
})()

在上面的代码中,当浏览器不支持此API时,我们代替的使用了setTimeout来保证了方法的向后兼容。
当然,这个polyfill没有考虑到需要连带使用cancelAnimationFrame的情况。如果有需要的同学,可以自己去搜索一下,在此就不再多言。

OK,关于requestAnimationFrame的相关内容,比较简单,就介绍到这里,多谢阅读。

----本文结束感谢阅读----