神奇的requestAnimationFrame

链接客
• 阅读 8912

引入

计时器一直是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也不会洗澡的(没通知啊)。页面绘制全部停止,资源高效利用。

总结:

setTimeoutsetInterval都不精确。它们的内在运行机制决定了时间间隔参数实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行。
requestAnimationFrame采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。

特点

  • requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率
  • 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量
  • requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销

名词解释

<div class="bi-table">

<colgroup><col width="181px"><col width="521px"></colgroup>
<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);

完美兼容

神奇的requestAnimationFrame
如果想要简单的兼容,可以这样子:

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);
        };
    }
}());
点赞
收藏
评论区
推荐文章
待兔 待兔
1年前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Souleigh ✨ Souleigh ✨
4年前
JavaScript 和 Node.js 中事件循环
1.JavaScript中事件循环可以参考《JavaScript忍者秘籍第二版》第十三章,讲解的很好。JavaScript中事件循环,主要就在理解宏任务和微任务这两种异步任务。宏任务(macrotask):setTimeOut、setInterval、setImmediate、I/O、各种callback、UI渲染、messageCh
Wesley13 Wesley13
4年前
SEO SEM
SEO:搜索引擎优化SEM:搜索引擎营销SEO排名机制:搜索引擎蜘蛛权重算法排名规则搜索引擎提交入口:1.百度搜索网站登入口2.Google网站登入口3.360搜索引擎登入入口4.搜狗网站登入入口5.必应网站等等SEO优化最重要的三要素:标题关键词描述外链(如友情链接)引流
徐小夕 徐小夕
5年前
如何使用css3实现一个类在线直播的队列动画
之前在群里有个朋友问了这样一个问题,就是如何在小程序中实现类似直播平台的用户上线时的队列动画?作为一名前端工程师,解决方案无非以下2种:1.使用javascript根据条件来控制元素的样式实现队列动画2.用纯css3配合数据驱动模型来实现.大家都知道在现代的Web开发中,我们能使用Css实现的效果尽量不要用Js,所以我们应该优先考虑用C
亚瑟 亚瑟
4年前
Flutter - 深入理解Flutter动画原理
基于Flutter1.5,从源码视角来深入剖析flutter动画原理,相关源码目录见文末附录一、概述动画效果对于系统的用户体验非常重要,好的动画能让用户感觉界面更加顺畅,提升用户体验。1.1动画类型Flutter动画大的分类来说主要分为两大类:补间动画:给定初值与终值,系统自动补齐中间帧的动画物理动画:遵循物理学定律
Chase620 Chase620
4年前
CSS3动画之逐帧动画
CSS3动画开发指南第二弹,剥丝抽茧为你解析逐帧动画,同时放送从实战经验中总结出来的逐帧动画使用技巧。什么是逐帧动画要了解CSS3逐帧动画,首先要明确什么是逐帧动画。看一下维基百科中的定义:定格动画,又名逐帧动画,是一种动画技术,其原理即将每帧不同的图像连续播放,从而产生动画效果。简而言之,实现逐帧动画需要两个条件:(1)相关联
陈占占 陈占占
4年前
Django 登入,注册,密码重置
先创建Django项目:配置settings.py和MySQL我不说了,自己网上配置xiangmu.js文件我的MySQL:链接MySQL,映射数据库我就不说了我是把MySQL的表给映射过来的【inspectdb表名】复制到models.py文件里,然后映射我的models.py文件:fromdjango.dbimportmodels
Wesley13 Wesley13
4年前
JS动画与CSS动画
一、JS动画(setIntervalsetTimeOutrequestAnimationFrame)优点:  1)过程控制能力强。可以对动画工程进行精准的控制,暂停、取消、开始、终止都可以。  2)动画效果多、炫酷。有一些效果是CSS动画所不能实现的  3)兼容性比较高缺点:   1)由于JS是通过不断的操
Wesley13 Wesley13
4年前
H5实现APP和原生方式有多大差距,多少坑?JS才是王道!
纯H5的APP,虽然开发起来要比纯原生开发畅快的多,但最终效果和性能还是和原生比起来还是有很多问题,主要聚集在以下几个方面:1、动画动画有很多种,比如侧边栏菜单的滑入滑出、元素的响应动画、页面切换之间的过场等等,在H5之下的众多实现方法都没有办法达到纯原生的性能。一般有这几种不同的选择:css3动画,javascript动画,原生动画。cs
绣鸾 绣鸾
2年前
Moho Pro 14 for Mac(2D动画制作软件)
是一款专业的2D动画制作软件。它具有强大的功能和工具,适用于动画制作师、插画师和设计师。MohoPro14提供了丰富的动画功能,包括骨骼动画、形状插值、帧逐帧动画等。您可以使用骨骼工具来创建复杂的角色动画,通过设置骨骼关节和控制器,实现自然流畅的动作。形状
陈杨 陈杨
8个月前
HarmonyOS5云服务技术分享--ArkTS调用函数
✨【HarmonyOS实战指南】手把手教你用ArkTS玩转云函数文件获取✨大家好呀今天我们来聊聊如何通过HarmonyOS的ArkTS语言实现云函数文件获取功能。整个过程就像搭积木一样有趣,保证小白也能轻松上手!(文末有完整代码模板哦)🔧前期准备1️⃣登
链接客
链接客
Lv1
我们可以普通,但我们必须拒绝平庸。
文章
5
粉丝
0
获赞
0