requestAnimationFrame的概述
requestAnimationFrame
是浏览器用于定时循环操作的一个接口,类似于setTimeout
,主要用途是按帧对网页进行重绘。requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧,即是时间间隔为1000/60=16.7ms。
requestAnimationFrame 与 setTimeout相比,最大的优势在于requestAnimationFrame是由系统来决定回调函数的执行时机,充分利用显示器的刷新机制,比较节省系统资源。当页面处于不可见或不可用状态时,浏览器就会停止动画,这意味着更少的CPU和更少的内存消耗。
浏览器丢帧的原因
在使用requestAnimationFrame
之前,若是我们使用js制作动画效果的话,一般都是通过setTimeout
和setInterval
来实现的,但是,你在setTimeout或setInterval中指定的回调函数的执行时机是无法保证的,它将在这一帧动画的某个时间点被执行,很可能是在帧结束的时候,这就意味这我们可能失去这一帧的信息。
举个例子,当动画使用10ms的JavaScript计时器分辨率绘制动画时,你将看到一个时序不匹配,如下所示。
最上面的一行代表大多数监视器上显示的16.7ms显示频率,最下面的一行代表10ms的典型setTimeout。由于在显示刷新间隔之前发生了另一个绘制请求,因此无法绘制每三个绘制(用红色箭头指示)。这种透支会导致动画断断续续,因为每三帧都会丢失。计时器分辨率的降低也会对电池寿命产生负面影响,并降低其他应用程序的性能。
requestAnimationFrame方法(在万维网联盟(W3C)的定义为基于脚本的动画的定时控制规范)可以解决丢帧问题,因为它使应用程序时通知(且仅当)的浏览器需要更新页面显示。因此,应用程序与浏览器绘画间隔完美匹配,并且仅使用适当数量的资源。从setTimeout切换到 requestAnimationFrame很容易,因为它们都安排了一个回调。对于连续动画,在调用动画函数之后再次调用requestAnimationFrame。
使用requestAnimationFrame
requestAnimationFrame
使用一个回调函数作为参数。这个回调函数会在浏览器重绘之前调用。
requestID = window.requestAnimationFrame(callback);
使用requestAnimationFrame的时候,只需反复调用它即可。
function repeatOften() {
// Do whatever
requestAnimationFrame(repeatOften);
}
requestAnimationFrame(repeatOften)
使用cancelAnimationFrame
方法取消重绘,它的参数是requestAnimationFrame返回的一个代表任务ID的整数值。
window.cancelAnimationFrame(requestID);
requestAnimationFrame的兼容性
由于requestAnimationFrame目前还存在兼容性问题,而且不同的浏览器还需要带不同的前缀,如果不支持requestAnimationFrame和cancelAnimationFrame,则使用setTimeout和clearTimeout。兼容性封装:
- 简化版
window.requestAnimationFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback) {
//为了使setTimteout的尽可能的接近每秒60帧的效果
window.setTimeout(callback, 1000 / 60)
}
window.cancelAnimationFrame = window.cancelAnimationFrame ||
Window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
window.msCancelAnimationFrame ||
function (id) {
window.clearTimeout(id)
};
- 升级版
(function() {
var lastTime = 0;
var vendors = ['webkit', 'moz', 'ms'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() {
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}
}());
好了,我们大概了解了requestAnimationFrame函数,那么我们接下来就学以致用,制作一个进度条效果吧
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>使用requestAnimationFrade制作进度条</title>
<style>
.container {
width: 400px;
margin: 50px auto;
}
.wrap {
height: 10px;
border-radius: 5px;
overflow: hidden;
background-color: #31493C;
}
.box {
height: 6px;
background-color: #B3EFB2;
border-radius: 3px;
margin: 2px;
}
.row {
display: flex;
justify-content: center;
align-items: center;
}
.btn {
cursor: pointer;
margin: 0 10px;
}
</style>
</head>
<body>
<div class="container">
<div class="wrap">
<div class="box" id="box"></div>
</div>
<div class="row">
<div class="txt"><span id="txt">0</span>%</div>
<div class="btn" id="btn">暂停</div>
</div>
</div>
<script>
let box = document.getElementById('box');
let btn = document.getElementById('btn');
let handel = 0;
let $width = 0;
function setWidth (params) {
box.style.width = $width + 'px';
handel = window.requestAnimationFrame(setWidth);
$width <= 396 ? $width++ : btn.style.display = 'none';
document.getElementById('txt').innerText = Math.ceil($width/400*100)
}
setWidth();
btn.addEventListener('click', function () {
if (handel) {
window.cancelAnimationFrame(handel);
handel = 0;
btn.innerText = '播放'
} else {
setWidth();
btn.innerText = '暂停'
}
}, false)
</script>
</body>
</html>
参考资料
- 阮一峰,requestAnimationFrame
- 张鑫旭,CSS3动画那么强,requestAnimationFrame还有毛线用?
- Paul Irish,requestAnimationFrame for Smart Animating
- caniuse,requestAnimationFrame
- Timing control for script-based animations ("requestAnimationFrame")