全面了解requestAnimationFrame

一、前言

由于最近在做一些新尝试,瞄上了HTML5的新特性requestAnimationFrame,发现真是好用,比计时器好了不知......但话不能说太满,各有各的好处吧...,下面就来大致详细简单聊聊requestAnimationFrame到底是个啥?怎么用?有啥特色?

二、概述

requestAnimationFrame字面意思:请求动画帧。官方解释:帧动画。就是可以一帧一帧的执行动画。

那么问题来了,这个一帧的执行频率是多久?答案是:与屏幕的刷新频率同步。

也可以认为:让浏览器在显示器屏幕下次刷新时,执行一帧;那么显示器多次刷新屏幕,就执行了多帧;如果速度够快,就会形成动画。

那么显示器的刷新屏幕,也就是屏幕的刷新频率又是什么呢?

三、屏幕刷新及刷新频率

要想理解屏幕刷新频率,就要先了解显示器及显示器的原理。

目前市面上常见的显示器有两种,即CRT和LCD, CRT就是传统的大头显示器(其实现在CRT显示器已经很少见了),LCD就是我们常说的液晶显示器。

CRT显示器
LED 显示器

屏幕刷新频率是对于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,没有兼容性是不可能的,但只要做好处理,且在不考虑低版本浏览器的情况下,还是非常值得推荐使用的。


requestAnimationFrame 兼容性

cancelAnimationFrame 兼容性

某些略低版本的浏览器还可以通过添加前缀的方式解决兼容问题,最后我们将上一段代码的封装,考虑前缀后,略作改进:

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


以上,如有纰漏或不同观点,欢迎留言讨论...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容