你有用过requestAnimationFrame吗?

前言

在自己练习动画逻辑的时候,到线上看别人写的实例,发现大家都在用requestAnimationFrame方法来处理下一帧的渲染,我因此也产生疑问,requestAnimationFrame到底是什么?他的优势在哪?

一、API介绍

1、什么是requestAnimationFrame

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。

简单来说,requestAnimationFrame 就是约定在下一次浏览器刷新前执行的一个定时器。类似setTimeout

2、语法

  var ID = requestAnimationFrame(callback);
  • 参数:callback是requestAnimationFrame的回调函数。该回调函数会被传入DOMHighResTimeStamp参数,该参数与performance.now()的返回值相同。浏览器下一次刷新时就会执行这个回调函数。
  • 返回值:ID是requestAnimationFrame的调用时的返回值,是其在回调列表中的唯一的标识。你可以传这个值给 window.cancelAnimationFrame(ID)以取消本次回调函数。

3、注意点

刚开始接触requestAnimationFrame时,大家经常有疑问:requestAnimationFrame什么时候执行?执行后时浏览器刷新都会执行回调函数吗?下边做简单回答(说多了,我就露馅了)

  • 执行时机: 你调用一次requestAnimationFrame,从调用开始计时,下次浏览器刷新前,会执行回调函数。需要注意的是 :调用一次,就只执行一次回调,若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用requestAnimationFrame(类似递归,下边会有介绍)
  • 执行时间间隔:每次执行时间间隔会有差异,回调函数会被传入DOMHighResTimeStamp参数,DOMHighResTimeStamp指示当前被 requestAnimationFrame() 排序的回调函数被触发的时间。在同一个帧中的多个回调函数,它们每一个都会接受到一个相同的时间戳,即使在计算上一个回调函数的工作负载期间已经消耗了一些时间。该时间戳是一个十进制数,单位毫秒,最小精度为1ms(1000μs)。

4、和setInterval、setTimeout的区别

上边提到requestAnimationFrame可以理解是一个定时器,大家就肯定想到了setInterval、setTimeout,他们有什么区别?

  • 队列执行优先级不同:三者都是是异步 api,setTimeout 和 setInterval属于宏任务; requestAnimationFrame属于“渲染任务”(调用GUI 引擎),执行优先级在宏任务前,微任务之后。
//每个tick执行逻辑如下:
...->上一个宏任务 -> 微任务(下一个宏任务前的所有微任务) -> 渲染任务 -> 下一个宏任务 ->...

代码测试如下:

setTimeout(function(){
  console.log("我是setTimeout", new Date().getTime())
},5)

requestAnimationFrame(function(){ //浏览器刷新频率是16.7ms左右,远大于5ms
  console.log("我是requestAnimationFrame", new Date().getTime());
})

new Promise(function(resolve){
  resolve('我是微任务')
}).then(res=>{
  console.log(res, new Date().getTime());
})
//下边是打印信息:

//我是微任务 1662356658402
//我是requestAnimationFrame 1662356658403
//我是setTimeout 1662356658408

setTimeout写在requestAnimationFrame前,却在requestAnimationFrame后执行,而且通过打印时间戳可以看到相隔时间就是5ms左右,就是说明在执行requestAnimationFrame 后setTimeout才开始执行

典型的 MacroTask(宏任务) 包含了 setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering等;
MicroTask(微任务)包含了 process.nextTick, Promises, Object.observe, MutationObserver等。

  • 定时时间不同:setTimeout 和 setInterval的定时时间是我们自己定的,而requestAnimationFrame的定时时间是浏览器的刷新间隔时间(一般浏览器1s刷新60次,刷新间隔时间大约为16.7ms,所以不同浏览器刷新频率不同会导致定时时间不同),不用自己计算渲染时间,而且不会掉帧卡顿。

二、应用

1、基本应用

用法和setTimeout差不多,只是不用写定时时间。需要注意的是每调用一次requestAnimationFrame,只在下次浏览器刷新前执行一次回调函数,如果希望多次连贯执行回调,则需要在回调中再次通过requestAnimationFrame调用回调函数(递归)。

var animationId;//用来赋值requestAnimationFrame的id,为之后取消它做准备
function step(){
    console.log('我就是下次浏览器刷新前需要执行的下一帧动画');
    animationId = requestAnimationFrame(step);//为了在之后的每次浏览器刷新前都执行回调,递归调用回调
}
animationId = requestAnimationFrame(step);//最开始的调用
···
cancelAnimationFrame(animationId)//在满足某个条件时,取消上边requestAnimationFrame的调用,终止无休止的执行回调。

2、兼容处理

firefox、chrome、ie10以上, requestAnimationFrame 的支持很好,但不兼容 IE9及以下浏览器,所以需要在多个不同浏览器运行的话,就要做兼容性处理。


在不同浏览器的兼容情况
//简单的兼容性处理
window.requestAnimationFrame = (function() {
  return window.requestAnimationFrame ||
         window.webkitRequestAnimationFrame ||
         window.mozRequestAnimationFrame ||
         function(callback) {
          window.setTimeout(callback, 1000/60);
         }
})();

上边这个简单的兼容处理还是存在问题的,因为并不是所有的设备的绘制时间间隔是1000/60ms,以及上面并没有cancel相关方法,所以,就有了下面这份更全面的兼容方法.

(function() {
  var lastTime = 0;
  var vendors = ['webkit', 'moz'];
  //如果window.requestAnimationFrame为undefined先尝试浏览器前缀是否兼容
  for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
    window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
    window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] ||//webkit中此取消方法的名字变了
                                  window[vendors[x] + 'CancelRequestAnimationFrame'];
  }
  //如果仍然不兼容,则使用setTimeOut进行兼容操作
  if(!window.requestAnimationFrame) {
    window.requestAnimationFrame = function(callback, element) {
      var currTime = new Date().getTime();
      var timeToCall = Math.max(0, 16.7 - (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);
    }
  }
})();

上述的代码是由Opera浏览器的技术师Erik Möller设计的,使得更好得兼容各种浏览器,但基本上他的代码就是判断使用4ms还是16ms的延迟,来最佳匹配60fps。

三、总结和补充

1、requestAnimationFrame优点

requestAnimationFrame被专门用来处理动画,自然有他的优点存在的

  • 动画流畅:动画每一帧的执行的间隔时间紧跟浏览器的刷新频率,动画更流畅,不会掉帧。
  • 节能
    1. 首先,在隐藏或不可见的元素中,requestAnimationFrame 将不会进行重绘或回流;
    2. requestAnimationFrame 是由浏览器专门为动画提供的 API,在运行时浏览器会自动优化方法的调用,如果浏览器在后台运行或者该页面tab在后台运行时,动画会自动暂停。

2、渲染引擎GUI

GUI 渲染引擎,用来处理浏览器的渲染操作,在 js 中渲染操作也是异步的。比如 操作DOM的代码会在事件队列中生成一个渲染任务,js 执行到这个任务时就会去调用 GUI 引擎渲染。

浏览器为了能够使得JS内部macro-task(宏任务)与DOM任务能够有序的执行(如果在 GUI 渲染的时候,js 改变了dom,那么就会造成渲染不同步),会在一个macro-task执行结束后,在下一个macro-task执行开始前,调用 GUI 引擎渲染对页面进行重新渲染,并会阻塞 js引擎计算。而micro-task(微任务)不涉及DOM操作,在渲染任务前执行。

3、屏幕绘制频率相关补充

即图像在屏幕上更新的速度,也即屏幕上的图像每秒钟出现的次数,它的单位是赫兹(Hz)。 对于一般笔记本电脑,这个频率大概是60Hz, 可以在桌面上 右键 > 屏幕分辨率 > 高级设置 > 监视器 中查看和设置。这个值的设定受屏幕分辨率、屏幕尺寸和显卡的影响,原则上设置成让眼睛看着舒适的值都行。
市面上常见的显示器有两种,即 CRT和 LCD, CRT 是一种使用阴极射线管(Cathode Ray Tube)的显示器,LCD 就是我们常说的液晶显示器( Liquid Crystal Display)。
CRT 是一种使用阴极射线管的显示器,屏幕上的图形图像是由一个个因电子束击打而发光的荧光点组成,由于显像管内荧光粉受到电子束击打后发光的时间很短,所以电子束必须不断击打荧光粉使其持续发光。电子束每秒击打荧光粉的次数就是屏幕绘制频率。
而对于 LCD 来说,则不存在绘制频率的问题,因为 LCD 中每个像素都在持续不断地发光,直到不发光的电压改变并被送到控制器中,所以 LCD 不会有电子束击打荧光粉而引起的闪烁现象。
因此,当你对着电脑屏幕什么也不做的情况下,显示器也会以每秒60次的频率正在不断的更新屏幕上的图像。为什么你感觉不到这个变化? 那是因为人的眼睛有视觉停留效应,即前一副画面留在大脑的印象还没消失,紧接着后一副画面就跟上来了,这中间只间隔了16.7ms(1000/60≈16.7), 所以会让你误以为屏幕上的图像是静止不动的。而屏幕给你的这种感觉是对的,试想一下,如果刷新频率变成1次/秒,屏幕上的图像就会出现严重的闪烁,这样就很容易引起眼睛疲劳、酸痛和头晕目眩等症状。

4、参考文章:

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

推荐阅读更多精彩内容