引入
我们平时实现JavaScript动画效果时离不开setInterval或者setTimeout函数,这两个函数本质上是相同的,都是计时器函数。而编写动画循环的关键是要知道延迟时间多长合适。一方面,循环间隔必须足够短,这样才能让不同的动画效果显得平滑流畅;另一方面,循环间隔还要足够长,这样才能确保浏览器有能力渲染产生的变化。
大多数电脑显示器的刷新频率是60Hz,大概相当于每秒钟重绘60次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升。因此,最平滑动画的最佳循环间隔是1000ms/60,约等于16.6ms。
而setTimeout和setInterval的问题是,它们都不精确。无法和显示器的刷新频率无法对应。它们的内在运行机制决定了时间间隔参数实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行。
requestAnimationFrame采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。
因为requestAnimationFrame的调用时间则是跟着系统的刷新频率走的,所以在实现动画的时候,setTimeout比requestAnimationFrame更加灵活, requestAnimationFrame比setTimeout表现效果更加优秀。
requestAnimationFrame
window.requestAnimation()告诉浏览器----你希望执行一个动画,并且要求浏览器在下次重绘(当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程)之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行(若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame())
准备更新动画时你应该调用此方法。这将使浏览器在下一次重绘之前调用你传入给该方法的动画函数(即你的回调函数)。回调函数执行次数通常是每秒60次,但在大多数遵循W3C建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。为了提高性能和电池寿命,因此在大多数浏览器里,当requestAnimationFrame()
运行在后台标签页或者隐藏的<iframe>
里时,requestAnimationFrame()
会被暂停调用以提升性能和电池寿命。
回调函数会被传入DOMHighResTimeStamp
参数(是一个相对的时间毫秒数,表示当前的刷新时间),DOMHighResTimeStamp
指示当前被 requestAnimationFrame()
排序的回调函数被触发的时间。在同一个帧中的多个回调函数,它们每一个都会接受到一个相同的时间戳,即使在计算上一个回调函数的工作负载期间已经消耗了一些时间。该时间戳是一个十进制数,单位毫秒,最小精度为1ms(1000μs)。
使用
window.requestAnimationFrame(callback);
// 参数:callback 下一次重绘之前更新动画帧所调用的函数(即上面所说的回调函数)。
// 该回调函数会被传入DOMHighResTimeStamp
参数,该参数与performance.now()
的返回值相同,
// 它表示requestAnimationFrame()
开始去执行回调函数的时刻。
// 返回值:一个long
整数,请求 ID ,是回调列表中唯一的标识。
// 可以传这个值给window.cancelAnimationFrame()
以取消回调函数。
使用范例
// 实现元素从左移动到右的动画
var start = null; var element = document.getElementById('SomeElementYouWantToAnimate'); element.style.position = 'absolute'; function step(timestamp) { if (!start) start = timestamp; var progress = timestamp - start; element.style.left = Math.min(progress / 10, 200) + 'px'; if (progress < 2000) {
// 如果执行效果没达到,继续执行 window.requestAnimationFrame(step); } } window.requestAnimationFrame(step);
兼容
因为是H5新增的属性,在PC浏览器上会出现兼容性问题,在移动端浏览器中可以随意使用。
- 简单兼容
if (!window.requestAnimationFrame) { requestAnimationFrame = function(fn) { // 这里不一定每次间隔都为17秒,因此会有偏差 setTimeout(fn, 17); }; }
- 严格兼容
if(!window.requestAnimationFrame){ var lastTime = 0; window.requestAnimationFrame = function(callback){ var currTime = new Date().getTime(); var timeToCall = Math.max(0,16.7-(currTime - lastTime)); var id = window.setTimeout(function(){ callback(currTime + timeToCall); },timeToCall); lastTime = currTime + timeToCall; return id; } }
requestAnimationFrame 比起 setTimeout、setInterval的优势:
- requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。
- 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量。
- requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销
参考:https://www.jianshu.com/p/a4edf06b2f19
https://segmentfault.com/a/1190000020639465?utm_source=tag-newest