背景
前几天看js性能优化的时候,看见requestAnimationFrame这个函数,其实以前也有见到多这个函数,当时只搜了一下它的用法,并没有深入的了解,这次趁热打铁,进行了进一步的了解
- requestAnimationFrame到底是个啥?干嘛用的?
嗯,那就先来说说这个函数是个啥吧!!!
我们在做动画效果的时候,对于js通常会用到setTimeout or setInterval,但是定时器的间隔时间并不怎么准确。requestAnimationFrame的作用和定时器差不多,是浏览器专门为动画提供的API,相比定时器,多了一些优势。它的回调函数执行间隔时间时和显示器刷新频率是一致的。 - requestAnimationFrame的用法
1.handle = requestAnimationFrame(callback);
2.cancelAnimationFrame(handle)
第一点:
handle:一个非零整数,是在requestAnimationFrame回调函数列表中的唯一标识。在删除动画的时候,就需要用到这个唯一标识了
callback:回调函数,想要实现的效果都写在这里。下次重新绘制的时候执行,只接受一个参数,一个高精度时间戳,是函数执行时的时间。
第二点:
取消操作,无返回值
先看一个栗子
let deg = 0;
let id = null;
let box = document.querySelector('.box')
let stop = document.querySelector('.stop')
box.onclick = function() {
requestAnimationFrame(function change() {
deg++;
box.style.transform = 'rotate('+ deg +'deg)'
id = requestAnimationFrame(change)
})
}
stop.onclick = function() {
cancelAnimationFrame(id)
}
- 原理
动画帧请求回调函数列表:每个Document都有一个动画帧请求函数列表,每个元素可看成是<handle,callback>元组。开始时,列表是空的。当调用requestAnimationFrame函数时,会将包含回调函数的元组加入到动画帧请求回调函数列表的末尾。而当这个列表不为空时,浏览器会定期
采样所有动画
,并且将其加入到列队中。
关于采样所有动画
任务的执行步骤,这篇文章:深入理解requestAnimationFrame用伪代码来进行了说明,如下:
var list = {};
var browsingContexts = 浏览器顶级上下文及其下属的浏览器上下文;
for (var browsingContext in browsingContexts) {
/*!将时间值从 DOMTimeStamp 更改为 DOMHighResTimeStamp 是 W3C 针对基于脚本动画计时控制规范的最新编辑草案中的最新更改,并且某些供应商仍将其作为 DOMTimeStamp 实现。较早版本的 W3C 规范使用 DOMTimeStamp,允许你将 Date.now 用于当前时间。
如上所述,某些浏览器供应商可能仍实现 DOMTimeStamp 参数,或者尚未实现 window.performance.now 计时函数。因此需要用户进行polyfill
*/
//var time = 从1970年1月1日到当前所经过的毫秒数;
var time = DOMHighResTimeStamp //从页面导航开始时测量的高精确度时间。DOMHighResTimeStamp 以毫秒为单位,精确到千分之一毫秒。此时间值不直接与 Date.now() 进行比较,后者测量自 1970 年 1 月 1 日至今以毫秒为单位的时间。如果你希望将 time 参数与当前时间进行比较,请使用当前时间的 window.performance.now。
var d = browsingContext的active document; //即当前浏览器上下文中的Document节点
//如果该active document可见
if (d.hidden !== true) {
//拷贝active document的动画帧请求回调函数列表到list中,并清空该列表
var doclist = d的动画帧请求回调函数列表
doclist.appendTo(list);
clear(doclist);
}
//遍历动画帧请求回调函数列表的元组中的回调函数
for (var callback in list) {
if (callback.cancelled !== true) {
try {
//每个browsingContext都有一个对应的WindowProxy对象,WindowProxy对象会将callback指向active document关联的window对象。
//传入时间值time
callback.call(window, time);
}
//忽略异常
catch (e) {
}
}
}
}
-
页面可见:当页面最小化或者被切换成后台标签页时,页面为不可见浏览器会触发一个
visibilitychange
事件,并将document.hidden
属性设置为true;页面切换到显示状态时,页面可见,也同样触发一个visibilitychange
事件,设置document.hidden
为false。 -
浏览器的执行过程:
浏览器的执行的过程中,会先判断页面是否可见(即判断document.hidden
的属性值),
若可见,则将当前浏览器上下文中的Doucment节点的动画帧请求回调函数列表拷贝到list函数中,并清空该列表。最后遍历动画请求回调列表元组中的回调函数。
若不可见,则不执行
let count = 0;
let id = null;
requestAnimationFrame(function change() {
if(count < 500){
count++;
id = requestAnimationFrame(change)
}
console.log(count)
})
- notes:
当页面可见且不为空时,列表会被拷贝到list中,然后被清空。如果需要多次执行回调函数,则需要多次调用requestAnimationFrame函数,这也就是为什么在requestAnimationFrame需要递归的原因。
- 优点
- 防止掉帧
定时器及requestAnimationFrame的执行都是在内存中对图像属性进行改变,这个改变必须等到下次浏览器重绘的时候才会更新到屏幕上。若在浏览器重绘的这段时间之间图像属性多次改变,等到浏览器重绘的时候,只会将最后一帧的变化更新到屏幕上。
requestAnimationFrame则让回调函数执行的间隔时间与屏幕的刷新频率一致。这样就不会出现掉帧了。 - 减少CPU损耗
前面有说到,当页面不可见时,该页面的刷新任务会被系统暂停,requestAnimationFrame回调函数的执行与页面刷新同步,所以也不会执行;但是定时器会继续在后台执行任务。
let count = 0;
let timer = null;
timer = setInterval(function () {
if(count < 200){
count++;
console.log(count)
}else{
clearInterval(timer)
}
},10)