requestAnimationFrame 是什么
语法:requestAnimationFrame(callback)
requestAnimationFrame 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
⚠requestAnimationFrame()是一次性的
- 也就是说,若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用 requestAnimationFrame()
requestAnimationFrame 解决了什么问题?
- 它本质上解决了定时器时间间隔不稳定的问题,可以将它看作是 setInterval 和 setTimeout 更好的解决方案
- 因为显示屏的刷新频率是 60HZ,也就是每秒刷新 60 次。那么 1000ms 下,刷新 60 次所需时间为 1000/60≈16.67ms,这就是浏览器所显示的最大的刷新频率了。
- 如果刷新频率> 16.67ms,并不会提升用户体验;反之,我们需要找到靠近 16.67ms 的时间,也就是 16 或者 17,在这个时间下的动画就会显得比较平滑了。
假如将 setInterval 或 setTimeout 的时间间隔设置为 16.67,会怎么样?
setInterval(function () {}, 16.67);
众所周知,setInterval 和 setTimeout 都属于异步 API,并且也属于宏任务。那么它们就需要等待同步任务以及微任务完成以后,才会执行传入的回调函数。这就造成了一个问题:无法精准地将时间定位到 16.67,即使换成 16 或者 17,也无法精确定位。
那么,requestAnimationFrame 的出现就解决了以上问题。
首先,需要明确的一点是:requestAnimationFrame 不是由 JavaScript 控制的,而是由系统的时间间隔来控制。这样带来的好处就是,不会因为 JavaScript 代码,导致当前任务时间间隔不准的问题。
所以,requestAnimationFrame 的解决方式就是采用系统的时间间隔。
从而,requestAnimationFrame 与 setInterval、setTimeout 的区别就是:
- requestAnimationFrame 的时间间隔是由系统来控制,而非 setInterval、setTimeout 的时间间隔由我们来控制
- 因此,这就可以解释:在 requestAnimationFrame 的语法使用上,为什么不用指定时间间隔了
使用方法
let timer1 = requestAnimationFrame(function () {
console.log(1);
});
let timer2 = requestAnimationFrame(function () {
console.log(2);
});
let timer3 = requestAnimationFrame(function () {
console.log(3);
});
// 1 -> 2 -> 3
同样地,requestAnimationFrame 也可以像 setInterval、setTimeout 一样访问到当前的 timer
let timer1 = requestAnimationFrame(function () {
console.log(timer1);
});
let timer2 = requestAnimationFrame(function () {
console.log(timer2);
});
let timer3 = requestAnimationFrame(function () {
console.log(timer3);
});
// 1 -> 2 -> 3
与 clearTimeout 和 clearInterval 一样,requestAnimationFrame 也有着自己的清除定时器的 API(cancelAnimationFrame)。
但值得注意的是:window.cancelAnimationFrame 是一个 Experimental 功能,即是一个实验中的功能。那么意味着此功能某些浏览器尚在开发中,请参考浏览器兼容性表格以得到在不同浏览器中适合使用的前缀。
// requestID -> 先前调用window.requestAnimationFrame()方法时返回的ID
window.mozCancelAnimationFrame(requestID); // Firefox浏览器
let timer1 = requestAnimationFrame(function () {
console.log(timer1);
});
let timer2 = requestAnimationFrame(function () {
console.log(timer2);
});
let timer3 = requestAnimationFrame(function () {
console.log(timer3);
});
cancelAnimationFrame(timer1);
// 2 -> 3
兼容性处理
由于 requestAnimationFrame 是 HTML5 新增的一个 API,那么必然存在着兼容性的问题
if (!window.requestAnimationFrame) {
requestAnimationFrame = function (fn) {
setTimeout(fn, 17);
};
}
测试例子
以下导航条的测试例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<span style="font-size: 20px">setInterval</span>
<div
id="test"
style="
width: 0px;
height: 12px;
line-height: 12px;
background-color: lightblue;
margin: 10px 0;
"
>
0%
</div>
<span style="font-size: 20px">setTimeout</span>
<div
id="test1"
style="
width: 0px;
height: 12px;
line-height: 12px;
background-color: lightsalmon;
margin: 10px 0;
"
>
0%
</div>
<span style="font-size: 20px">requestAnimationFrame</span>
<div
id="test2"
style="
width: 0px;
height: 12px;
line-height: 12px;
background-color: lightgreen;
margin: 10px 0;
"
>
0%
</div>
<script>
// setInterval
let Test = document.getElementById("test");
Test.onclick = function () {
let timer = setInterval(function () {
if (parseInt(Test.style.width) < 300) {
Test.style.width = parseInt(Test.style.width) + 3 + "px";
Test.innerHTML = parseInt(Test.style.width) / 3 + "%";
} else {
clearInterval(timer);
}
}, 17);
};
// setTimeout
let Test1 = document.getElementById("test1");
Test1.onclick = function () {
let timer = setTimeout(function fn() {
if (parseInt(Test1.style.width) < 300) {
Test1.style.width = parseInt(Test1.style.width) + 3 + "px";
Test1.innerHTML = parseInt(Test1.style.width) / 3 + "%";
timer = setTimeout(fn, 17);
} else {
clearInterval(timer);
}
}, 17);
};
// requestAnimationFrame
let Test2 = document.getElementById("test2");
Test2.onclick = function () {
let timer = requestAnimationFrame(function fn() {
if (parseInt(Test2.style.width) < 300) {
Test2.style.width = parseInt(Test2.style.width) + 3 + "px";
Test2.innerHTML = parseInt(Test2.style.width) / 3 + "%";
timer = requestAnimationFrame(fn);
} else {
cancelAnimationFrame(timer);
}
});
};
</script>
</body>
</html>
效果视频
可以明显的看出,requestAnimationFrame 更快。当然,也不是非得用 requestAnimationFrame,只是在做相对复杂的动画效果时,它能带来更好的用户体验。