神奇的requestAnimationFrame
引入
计时器一直是JavaScript动画的核心技术。而编写动画循环的关键是要知道延迟时间多长合适。一方面,循环间隔必须足够短,这样才能让不同的动画效果显得平滑流畅;另一方面,循环间隔还要足够长,这样才能确保浏览器有能力渲染产生的变化。
如何保证流畅呢?登登登登....该requestAnimationFrame登场了~~
16.7ms的由来
大多数电脑显示器的刷新频率是60Hz
,大概相当于每秒钟重绘60
次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升。因此,最平滑动画的最佳循环间隔是1000ms/60
,约等于16.7ms
。
settimeout->OUT
同样的,显示器16.7ms
刷新间隔之前发生了其他绘制请求(setTimeout
),导致所有第三帧丢失,继而导致动画断续显示(堵车的感觉),这就是过度绘制带来的问题。不仅如此,这种计时器频率的降低也会对电池使用寿命造成负面影响,并会降低其他应用的性能。
这也是为何setTimeout
的定时器值推荐最小使用16.7ms
的原因(16.7 = 1000 / 60, 即每秒60帧)。
但...
1、即使向其传递毫秒为单位的参数,它们也不能达到ms的准确性。这是因为javascript是单线程的,可能会发生阻塞。
2、没有对调用动画的循环机制进行优化。
3、没有考虑到绘制动画的最佳时机,只是一味地以某个大致的事件间隔来调用循环。
...OUT
requestAnimationFrame登场
requestAnimationFrame不需要使用者指定循环间隔时间,浏览器会基于当前页面是否可见、CPU的负荷情况等来自行决定最佳的帧速率,跟着浏览器的绘制走,如果浏览设备绘制间隔是16.7ms
,那我就这个间隔绘制;如果浏览设备绘制间隔是10ms
, 我就10ms
绘制。
这样自然就合理地使用CPU,不会存在过度绘制的问题,动画不会掉帧,自然流畅的说~~
内部是这么运作的:
浏览器(如页面)每次重绘,就会通知我(
requestAnimationFrame
):嗨,我要重绘了,你可以跟我一起重绘哦!
这是资源非常高效的一种利用方式。怎么讲呢?
1.就算很多元素需要重绘,浏览器只要通知一次就可以了。而
setTimeout
貌似是多个独立绘制。2.页面最小化了,或者被Tab切换关灯了。页面是不会重绘的,自然,requestAnimationFrame也不会洗澡的(没通知啊)。页面绘制全部停止,资源高效利用。
总结:
setTimeout
和setInterval
都不精确。它们的内在运行机制决定了时间间隔参数实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行。
requestAnimationFrame
采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。
特点
requestAnimationFrame
会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率- 在隐藏或不可见的元素中,
requestAnimationFrame
将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量 requestAnimationFrame
是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销
名词解释
<div class="bi-table">
<div data-type="p">动画帧请求回调函数列表</div> | <div data-type="p">每个Document都有一个动画帧请求回调函数列表,该列表可以看成是由< handle, callback>元组组成的集合。其中handle是一个整数,唯一地标识了元组在列表中的位置;callback是一个无返回值的、形参为一个时间值的函数(该时间值为由浏览器传入的从1970年1月1日到当前所经过的毫秒数)。 刚开始该列表为空。</div> |
<div data-type="p">Document</div> | <div data-type="p">Dom模型中定义的Document节点</div> |
<div data-type="p">Active document</div> | <div data-type="p">浏览器上下文browsingContext中的Document被指定为active document</div> |
<div data-type="p">browsingContext</div> | <div data-type="p">即浏览器上下文。</div><div data-type="p">浏览器上下文是呈现document对象给用户的环境。 浏览器中的1个tab或一个窗口包含一个顶级浏览器上下文,如果该页面有iframe,则iframe中也会有自己的浏览器上下文,称为嵌套的浏览器上下文。 </div> |
<div data-type="p">页面可见</div> | <div data-type="p">当页面被最小化或者被切换成后台标签页时,页面为不可见,浏览器会触发一个 visibilitychange事件,并设置document.hidden属性为true;切换到显示状态时,页面为可见,也同样触发一个 visibilitychange事件,设置document.hidden属性为false</div> |
<div data-type="p">队列</div> | <div data-type="p">浏览器让一个单线程共用于执行javascrip和更新用户界面。这个线程通常被称为“浏览器UI线程”。 浏览器UI线程的工作基于一个简单的队列系统,任务会被保存到队列中直到进程空闲。一旦空闲,队列中的下一个任务就被重新提取出来并运行。这些任务要么是运行javascript代码,要么执行UI更新,包括重绘和重排。</div> |
</div>
API接口
Window对象定义了以下两个接口:
partial interface Window {long requestAnimationFrame(FrameRequestCallback callback);void cancelAnimationFrame(long handle);
};
requestAnimationFrame
1.requestAnimationFrame方法用于通知浏览器重采样动画。
当requestAnimationFrame(callback)被调用时不会执行callback,而是会将元组< handle,callback>插入到动画帧请求回调函数列表末尾(其中元组的callback就是传入requestAnimationFrame的回调函数),并且返回handle值,该值为浏览器定义的、大于0的整数,唯一标识了该回调函数在列表中位置。
2.每个回调函数都有一个布尔标识cancelled,该标识初始值为false,并且对外不可见。
3.在后面的“处理模型” 中我们会看到,浏览器在执行“采样所有动画”的任务时会遍历动画帧请求回调函数列表,判断每个元组的callback的cancelled,如果为false,则执行callback。
cancelAnimationFrame
1、cancelAnimationFrame 方法用于取消先前安排的一个动画帧更新的请求。
2、当调用cancelAnimationFrame(handle)时,浏览器会设置该handle指向的回调函数的cancelled为true。
无论该回调函数是否在动画帧请求回调函数列表中,它的cancelled都会被设置为true。
3.如果该handle没有指向任何回调函数,则调用cancelAnimationFrame 不会发生任何事情。
4.注意:在requestAnimationFrame的callback内部执行cancelAnimationFrame不能取消动画
处理模型
当页面可见并且动画帧请求回调函数列表不为空时,浏览器会定期地加入一个“采样所有动画”的任务到UI线程的队列中。
使用
requestAnimationFrame
的用法与setTimeout
很相似,只是不需要设置时间间隔而已。requestAnimationFrame
使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。它返回一个整数,表示定时器的编号,这个值可以传递给cancelAnimationFrame
用于取消这个函数的执行。
requestID = requestAnimationFrame(callback);
完美兼容
如果想要简单的兼容,可以这样子:
window.requestAnimFrame = (function(){return window.requestAnimationFrame ||window.webkitRequestAnimationFrame ||window.mozRequestAnimationFrame ||function( callback ){window.setTimeout(callback, 1000 / 60);};
})();
但是呢,并不是所有设备的绘制时间间隔是1000/60 ms
, 以及上面并木有cancel相关方法,所以,就有下面这份更全面的兼容方法:
(function() {var lastTime = 0;var vendors = ['webkit', 'moz'];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'];}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);};}
}());
神奇的requestAnimationFrame相关推荐
- 一段神奇的c代码错误分析
源代码 #include <stdio.h>int main(int argc, char* argv[]) {int i = 0;int arr[3] = {0};printf(&quo ...
- python deque双端队列的神奇用法
python中的deque双端队列,类似list的任意一端都可实现较快的add和pop操作 from collections import dequed=deque(maxlen=20) for i ...
- 几行代码实现神奇移动的过渡动画
1.效果如图: 2.实现: 假设需求为如上图,点击ViewController01后,ViewController01上的两张图片,移动到ViewContoller02中,其实两个ViewContro ...
- Hudson神奇的环境变量
Hudson神奇的环境变量 http://blog.sina.com.cn/s/blog_798f21a00100z6zw.html 转载于:https://blog.51cto.com/mylove ...
- 神奇的输入 while(cin....)如何在遇见换行之后进入下一层循环读入
1 cin>>m>>n; 2 for(int i=1;i<=m;i++) { 4 int x=0; 5 char ch=' '; 6 while(ch!=10) //在遇 ...
- 【NOIP2015提高组Day1】 神奇的幻方
[问题描述] 幻方是一种很神奇的 N*N矩阵:它由数字1,2,3, - - ,N*N 构成,且每行.每列及两条对角线上的数字之和都相同. 当N为奇数时,我们可以通过以下方法构建一个幻方: 首先将1写在 ...
- 图片提取文字功能很神奇?Java几行代码搞定它!
欢迎关注方志朋的博客,回复"666"获面试宝典 来源:blog.csdn.net/weixin_44671737/ article/details/110000864 摘要 近日浏 ...
- CVPR 2021:记一次神奇的 Rebuttal 经历
点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 作者丨信息门下遛狗@知乎(已授权) 来源丨https://zhuan ...
- 曾因「抢车位」出圈儿,神奇的Mask R-CNN了解一下?
来自:博文视点Broadview 自从深度学习被应用到计算机视觉领域,目标检测算法在短时间内有了很大的进步,甚至有人为了抢个车位用上了Mask R-CNN进行自动检测 能有这样的神仙操作,多亏了Mas ...
最新文章
- 腾讯集团与光大集团签署战略合作 共建金融科技创新实验室
- python绘制简单直方图-Python数据分析:统计函数绘制简单图形
- kbengine0.2.3发布,开源分布式游戏服务端引擎
- 用py2exe打包后的程序一闪而过
- 更新登录SAP后的LOGO
- Linux traceroute路由跟踪
- Pwn环境配置(一)——安装虚拟机
- Node — 第一天
- linux procfs文件系统(2)
- 科研项目 | 深度参与前沿课题研究,全英华人教授协会(ABCP)资深学者亲授
- .NET Core 3 Preview 2 发布,C# 8 更强大的模式匹配
- Python数据可视化的四种简易方法 1
- JSP中—request.getRequestDispatcher(“login_success.jsp“).forward(request,response)
- linux安装java、配置 jmeter
- hdu-5596 GTW likes gt(模拟+优先队列)
- 计算机组成原理(动态随机存储器)
- android自定义rx库,Android下载库(OkHttp3+Retrofit2+RxJava2)
- 求解TSP问题(python)(穷举、最近邻居法、opt-2法、动态规划、插入法)
- 计算机检测不到蓝牙,图解Win10 1809系统中检测不到蓝牙设备的方法
- openwrt路由器打印机服务器设置_openwrt路由器打印机服务器设置_TP-Link无线路由器打印机设置指南...