requestAnimationFrame 刨根问底
开头引用一段 Google Developer Rendering Performance:
当屏幕正在发生视觉变化时,您希望在适合浏览器的时间执行您的工作,也就是正好在帧的开头。保证 JavaScript 在帧开始时运行的唯一方式是使用 requestAnimationFrame。
框架或示例可能使用 setTimeout 或 setInterval 来执行动画之类的视觉变化,但这种做法的问题是,回调将在帧中的某个时点运行,可能刚好在末尾,而这可能经常会使我们丢失帧,导致卡顿。(事实上,jQuery 目前的默认 animate 行为是使用 setTimeout!)
什么是渲染帧?
这得从显示器的刷新频率说起,目前主流的LCD液晶显示器,刷新频率规格大多在60Hz。
60Hz什么概念呢,就是大约每16.66毫秒刷新一次屏幕,叫做一个渲染帧。
你现在看到的屏幕,就是用这种高速在不断的做一次又一次的渲染。
在这个渲染帧到下个渲染帧期间,加上JS线程和GUI线程之间的通信等损耗,你的代码必须在10ms左右完成才能保证不掉帧。
是不是看高速世界看得有些懵?
没关系,我们换一个老式CRT显示器
CRT显示器是靠电子束激发屏幕内表面的荧光粉来显示图像的,由于荧光粉被点亮后很快会熄灭,所以电子枪必须循环地不断激发这些点,电子束在屏幕上一行紧接一行从左到右的逐行扫描。
现在我们来放慢它的速度,假装它扫描整个屏幕要用10秒,够长了吧~现在再来看刚刚的操作。
我们一个动画小球在屏幕左边,接着我们执行了一行代码,它右移了一个像素。但是它没有马上呈现在画面中,而是等到逐行扫描过后,才出现。(还得自己画gif 〒▽〒)
同理,回到现代设备,60Hz的刷新频率也是如此处理。
这么短的时间,代码能执行完吗?
回答这个问题之前,我们来看看现代的CPU(拿i3举例)
1GHz是多少次脉冲呢?大概是1秒10亿次~吧~
1GHz的CPU如果只做加法运算,进行一次完整的加法运算需要读2个数据,8个周期+运算16个周期+写入6个周期总共需要30个时钟周期(注意,不同CPU需要的周期是不同的,这里只是举列),大概也还能1秒做个100万次。
所以这里还得再看看我们的代码算法难易度
如何查看我们的代码执行时间?
打开我们Chrome的开发者工具,选择JavaScript Profiler就可以看见了(可以用下面的示例代码跑一跑,感受一下)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
.ball {
width: 50px;
height: 50px;
position: absolute;
top: 0px;
left: 40%;
background: orange;
border-radius: 50%;
}
</style>
</head>
<body>
<div class="ball"></div>
<script>
function move() {
document.querySelector('.ball').style.top = 100 * Math.random() + 100 + 'px';
requestAnimationFrame(move);
// 可以打开setTimeout注释对比性能
// setTimeout(() => { move() })
}
move()
</script>
</body>
</html>
优势与兼容性
requestAnimationFrame还有以下两个优势:
CPU节能:使用setTimeout实现的动画,当页面被隐藏或最小化时,setTimeout 仍然在后台执行动画任务,由于此时页面处于不可见或不可用状态,刷新动画是没有意义的,完全是浪费CPU资源。而requestAnimationFrame则完全不同,当页面处理未激活的状态下,该页面的屏幕刷新任务也会被系统暂停,因此跟着系统步伐走的requestAnimationFrame也会停止渲染,当页面被激活时,动画就从上次停留的地方继续执行,有效节省了CPU开销。
函数节流:在高频率事件(resize,scroll等)中,为了防止在一个刷新间隔内发生多次函数执行,使用requestAnimationFrame可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销。一个刷新间隔内函数执行多次时没有意义的,因为显示器每16.7ms刷新一次,多次绘制并不会在屏幕上体现出来。
从图中也看出除了IE外的其他浏览器兼容性还是很不错的,大家也可以看看这篇优雅降级方案:
https://github.com/darius/requestAnimationFrame
总结
在写相关动画效果的时候,因当格外注意动画的代码,尽量在10ms内执行完成。与setTimeout相比,requestAnimationFrame最大的优势是由系统来决定回调函数的执行时机,在上个渲染帧结束后开始执行代码,规避出现掉帧的情况。
对技术感兴趣的同学可以Github互相关注一波~
https://github.com/cmyh100
--完--