requestAnimationFrame
requestAnimationFrame作用
告诉浏览器您希望执行动画并请求浏览器在下一次重绘之前调用指定的函数来更新动画。该方法将回调作为要在重绘之前调用的参数
requestAnimationFrame是浏览器用于定时循环操作的一个接口,类似于setTimeout,主要用途是按帧对网页进行重绘。
设置这个API的目的是为了让各种网页动画效果(DOM动画、Canvas动画、SVG动画、WebGL动画)能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。
代码中使用这个API,就是告诉浏览器希望执行一个动画,让浏览器在下一个动画帧安排一次网页重绘。
requestAnimationFrame的优势
在于充分利用显示器的刷新机制,比较节省系统资源。显示器有固定的刷新频率(60Hz或75Hz),也就是说,每秒最多只能重绘60次或75次,
requestAnimationFrame的基本思想就是与这个刷新频率保持同步,利用这个刷新频率进行页面重绘。此外,使用这个API,一旦页面不处于浏览器的当前标签,就会自动停止刷新。这就节省了CPU、GPU和电力。
1、CPU节能:
使用setTimeout实现的动画,当页面被隐藏或最小化时,setTimeout 仍然在后台执行动画任务,由于此时页面处于不可见或不可用状态,刷新动画是没有意义的,完全是浪费CPU资源。
而requestAnimationFrame则完全不同,当页面处理未激活的状态下,该页面的屏幕刷新任务也会被系统暂停,因此跟着系统步伐走的requestAnimationFrame也会停止渲染,当页面被激活时,
动画就从上次停留的地方继续执行,有效节省了CPU开销。
2、函数节流:
在高频率事件(resize,scroll等)中,为了防止在一个刷新间隔内发生多次函数执行,使用requestAnimationFrame可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销。
一个刷新间隔内函数执行多次时没有意义的,因为显示器每16.7ms刷新一次,多次绘制并不会在屏幕上体现出来。
注意:
requestAnimationFrame是在主线程上完成。这意味着,如果主线程非常繁忙,requestAnimationFrame的动画效果会大打折扣。
requestAnimationFrame使用一个回调函数作为参数。这个回调函数会在浏览器重绘之前调用。
案例
在这个例子中,一个元素的动画持续了 2 秒(2000 毫秒)。元素以 0.1px/ms 的速度向右移动,因此它的相对位置(以 CSS 像素为单位)可以根据从动画开始(以毫秒为单位)的时间函数来计算0.1 * elapsed。元素的最终位置是0.1 * 2000其初始位置右侧200 像素 ( )。
const element = document.getElementById('some-element-you-want-to-animate');
let start;
function step(timestamp) {
if (start === undefined)
start = timestamp;
const elapsed = timestamp - start;
element.style.transform = 'translateX(' + Math.min(0.1 * elapsed, 200) + 'px)';
if (elapsed < 2000) { //在动画还没结束前递归渲染
window.requestAnimationFrame(step);
}
}
//第一帧渲染
window.requestAnimationFrame(step);
2、
var progress = 0;
//回调函数
function render() {
progress += 1; //修改图像的位置
if (progress < 100) { //在动画没有结束前,递归渲染
window.requestAnimationFrame(render);
}
}
//第一帧渲染
window.requestAnimationFrame(render);
setTimeout与requestAnimationFrame相比
与setTimeout相比,requestAnimationFrame最大的优势是由系统来决定回调函数的执行时机。具体一点讲,如果屏幕刷新率是60Hz,那么回调函数就每16.7ms被执行一次,如果刷新率是75Hz,那么这个时间间隔就变成了1000/75=13.3ms,换句话说就是,requestAnimationFrame的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。
兼容性问题
由于requestAnimationFrame目前还存在兼容性问题,而且不同的浏览器还需要带不同的前缀。因此需要通过优雅降级的方式对requestAnimationFrame进行封装,优先使用高级特性,然后再根据不同浏览器的情况进行回退,直至只能使用setTimeout的情况。下面的代码就是有人在github上提供的polyfill,详细介绍请参考github代码 requestAnimationFrame。
if (!Date.now)
Date.now = function() { return new Date().getTime(); };
(function() {
'use strict';
var vendors = ['webkit', 'moz'];
for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {
var vp = vendors[i];
window.requestAnimationFrame = window[vp+'RequestAnimationFrame'];
window.cancelAnimationFrame = (window[vp+'CancelAnimationFrame']
|| window[vp+'CancelRequestAnimationFrame']);
}
if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) // iOS6 is buggy
|| !window.requestAnimationFrame || !window.cancelAnimationFrame) {
var lastTime = 0;
window.requestAnimationFrame = function(callback) {
var now = Date.now();
var nextTime = Math.max(lastTime + 16, now);
return setTimeout(function() { callback(lastTime = nextTime); },
nextTime - now);
};
window.cancelAnimationFrame = clearTimeout;
}
}());