一、前言
由于最近在做一些新尝试,瞄上了HTML5的新特性requestAnimationFrame
,发现真是好用,比计时器好了不知......但话不能说太满,各有各的好处吧...,下面就来大致详细简单聊聊requestAnimationFrame
到底是个啥?怎么用?有啥特色?
二、概述
requestAnimationFrame
字面意思:请求动画帧。官方解释:帧动画。就是可以一帧一帧的执行动画。
那么问题来了,这个一帧的执行频率是多久?答案是:与屏幕的刷新频率同步。
也可以认为:让浏览器在显示器屏幕下次刷新时,执行一帧;那么显示器多次刷新屏幕,就执行了多帧;如果速度够快,就会形成动画。
那么显示器的刷新屏幕,也就是屏幕的刷新频率又是什么呢?
三、屏幕刷新及刷新频率
要想理解屏幕刷新频率,就要先了解显示器及显示器的原理。
目前市面上常见的显示器有两种,即CRT和LCD, CRT就是传统的大头显示器(其实现在CRT显示器已经很少见了),LCD就是我们常说的液晶显示器。
屏幕刷新频率是对于CRT显示器来说的,因CRT显示器是一种使用阴极射线管的显示器,屏幕上的图形图像是由一个个因电子束击打而发光的荧光点组成,由于显像管内荧光粉受到电子束击打后发光的时间很短,所以电子束必须不断击打荧光粉使其持续发光。
对于LCD来说则不存在刷新率的问题,因为LCD中每个像素都在持续不断地发光,直到不发光的电压改变并被送到控制器中,所以LCD不会有“不断充放电”而引起的闪烁现象。
但是LCD沿用的CRT显示器屏幕刷新频率的概念,即图像在屏幕上更新的速度,也即屏幕上的图像每秒钟出现的次数,它的单位是赫兹(Hz)。 并且是可以设置的(但实验证明,调高一定数值之后并没有带来什么提升)。
对于一般笔记本电脑来说,这个频率大概是60Hz,表示每秒刷新60次屏幕。windows系统可以在桌面上右键->屏幕分辨率->高级设置->监视器中查看和设置。这个值的设定受屏幕分辨率、屏幕尺寸和显卡的影响,原则上设置成让眼睛看着舒适的值都行。
四、动画的原理
因此,当你对着电脑屏幕什么也不做的情况下,显示器也会以每秒60次的频率正在不断的更新屏幕上的图像。为什么你感觉不到这个变化? 那是因为人的眼睛有视觉停留效应,即前一副画面留在大脑的印象还没消失,紧接着后一副画面就跟上来了,这中间只间隔了16.7ms(1000/60≈16.7), 所以会让你误以为屏幕上的图像是静止不动的。而屏幕给你的这种感觉是对的,试想一下,如果刷新频率变成1次/秒,屏幕上的图像就会出现严重的闪烁,这样就很容易引起眼睛疲劳、酸痛和头晕目眩等症状。
所以,如果我们能够捕捉屏幕的刷新频率,并在每次屏幕刷新时,执行某个连续的改变,那么对于人眼来说,就形成了连贯的动画。
五、requestAnimationFrame的使用
有了以上知识储备之后,我们不难明白requestAnimationFrame
的执行原理。最后再补充些常识性内容:
-
requestAnimationFrame
是HTML5版本新增的API方法 - 被绑定在window对象身上
- 接收一个回调函数作为参数
- 返回值是当前执行的唯一标志,用来清除这次执行(与计时器类似)
注意没有时间参数,因为会自动随着屏幕的刷新频率自动执行,所以最终语法为:
let i = 0;
function step(timestamp) {
console.log(i++);
window.requestAnimationFrame(step);
}
window.requestAnimationFrame(step);
那么如何清除呢:
let myReq;
let i = 0;
function step(timestamp) {
console.log(i++);
myReq = window.requestAnimationFrame(step);
}
window.requestAnimationFrame(step);
document.onclick = function(){
window.cancelAnimationFrame(myReq); // 专属清除方式
}
点击页面就可以停止当前执行。
但是我们不难发现,其实这么使用requestAnimationFrame
实现动画,是一件非常不优雅的事。至少现在只能根据浏览器的刷新频率自动执行(一般按照60Hz算,每秒执行60次),而不能自定义每秒次数,所以我们可以简单封装一下:
function animate(cb,time){
let myReq; // 记录requestAnimationFrame的返回值
let i = 1; // 记录requestAnimationFrame的执行次数(屏幕刷新次数)
myReq = requestAnimationFrame(function fn(){ // 开启初始requestAnimationFrame
// 计数器 % (60/一秒钟执行的次数)
if(i%parseInt(60/(1000/time)) == 0){
cb(); // 执行真正要做的事情
}
i++; // 记录requestAnimationFrame执行的次数
myReq = requestAnimationFrame(fn); // 开启下次requestAnimationFrame
window.myReq = myReq; // 将requestAnimationFrame返回值暴露,方便清除
});
}
// 测试
animate(function(){
console.log("自己封装了个计时器,好厉害呀");
}, 1000); // 自定义执行时间
document.onclick = function(){
// 主动控制清除动画
cancelAnimationFrame(myReq);
}
六、requestAnimationFrame的特点
此处我们已经能够简单使用requestAnimationFrame
开启动画了,当然我们还需要考虑兼容型。
不着急,在考虑兼容性之前,我们先来说说requestAnimationFrame
和常规计时器相比,有什么特点:
CPU节能:使用setInterval实现的动画,当页面被隐藏或最小化时,setInterval仍然在后台执行动画任务。而
requestAnimationFrame
在页面未激活时,该页面的屏幕刷新任务也会被系统暂停。当页面被激活时,任务会从上次停留的地方继续执行,有效节省了CPU开销。流畅度:
requestAnimationFrame
由系统决定回调函数的执行时机。60Hz的刷新频率,每次刷新的间隔中会执行一次回调函数,不会引起丢帧,卡顿。而setInterval任务被放入异步队列,只有当主线程任务执行完后才会执行队列中的任务,因此实际执行时间总是比设定时间要晚;且setInterval的固定时间间隔不一定与屏幕刷新时间相同,会引起丢帧。函数节流:
requestAnimationFrame
可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销。
七、requestAnimationFrame的兼容
其实作为HTML5新增的API,没有兼容性是不可能的,但只要做好处理,且在不考虑低版本浏览器的情况下,还是非常值得推荐使用的。
某些略低版本的浏览器还可以通过添加前缀的方式解决兼容问题,最后我们将上一段代码的封装,考虑前缀后,略作改进:
const animate = (function(){
const requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
const cancelAnimationFrame = window.cancelAnimationFrame ||
window.mozCancelAnimationFrame;
return function(cb,time){
let myReq;
let i = 1;
myReq = requestAnimationFrame(function fn(){
// 计数器 % (60/一秒钟执行的次数)
if(i%parseInt(60/(1000/time)) == 0){
cb();
}
i++;
myReq = requestAnimationFrame(fn);
window.myReq = myReq;
});
}
})();
animate(function(){
console.log("自己封装了个计时器,好厉害呀");
}, 500);
document.onclick = function(){
cancelAnimationFrame(myReq);
}
参考资料:
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame
以上,如有纰漏或不同观点,欢迎留言讨论...