神奇的requestAnimationFrame

引入

计时器一直是JavaScript动画的核心技术。而编写动画循环的关键是要知道延迟时间多长合适。一方面,循环间隔必须足够短,这样才能让不一样的动画效果显得平滑流畅;另外一方面,循环间隔还要足够长,这样才能确保浏览器有能力渲染产生的变化。javascript

如何保证流畅呢?登登登登....该requestAnimationFrame登场了~~java

16.7ms的由来

大多数电脑显示器的刷新频率是60Hz,大概至关于每秒钟重绘60次。大多数浏览器都会对重绘操做加以限制,不超过显示器的重绘频率,由于即便超过那个频率用户体验也不会有提高。所以,最平滑动画的最佳循环间隔是1000ms/60,约等于16.7msweb

settimeout->OUT

一样的,显示器16.7ms刷新间隔以前发生了其余绘制请求(setTimeout),致使全部第三帧丢失,继而致使动画断续显示(堵车的感受),这就是过分绘制带来的问题。不只如此,这种计时器频率的下降也会对电池使用寿命形成负面影响,并会下降其余应用的性能。浏览器

这也是为什么setTimeout的定时器值推荐最小使用16.7ms的缘由(16.7 = 1000 / 60, 即每秒60帧)。函数

但...

一、即便向其传递毫秒为单位的参数,它们也不能达到ms的准确性。这是由于javascript是单线程的,可能会发生阻塞。性能

二、没有对调用动画的循环机制进行优化。优化

三、没有考虑到绘制动画的最佳时机,只是一味地以某个大体的事件间隔来调用循环。动画

...OUT

requestAnimationFrame登场

requestAnimationFrame不须要使用者指定循环间隔时间,浏览器会基于当前页面是否可见、CPU的负荷状况等来自行决定最佳的帧速率,跟着浏览器的绘制走,若是浏览设备绘制间隔是16.7ms,那我就这个间隔绘制;若是浏览设备绘制间隔是10ms, 我就10ms绘制。
这样天然就合理地使用CPU,不会存在过分绘制的问题,动画不会掉帧,天然流畅的说~~spa

内部是这么运做的:线程

浏览器(如页面)每次重绘,就会通知我(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

一、cancelAnimationFrame 方法用于取消先前安排的一个动画帧更新的请求。
二、当调用cancelAnimationFrame(handle)时,浏览器会设置该handle指向的回调函数的cancelled为true。
不管该回调函数是否在动画帧请求回调函数列表中,它的cancelled都会被设置为true。
3.若是该handle没有指向任何回调函数,则调用cancelAnimationFrame 不会发生任何事情。
4.注意:在requestAnimationFrame的callback内部执行cancelAnimationFrame不能取消动画

处理模型

当页面可见而且动画帧请求回调函数列表不为空时,浏览器会按期地加入一个“采样全部动画”的任务到UI线程的队列中。

使用

requestAnimationFrame的用法与setTimeout很类似,只是不须要设置时间间隔而已。requestAnimationFrame使用一个回调函数做为参数,这个回调函数会在浏览器重绘以前调用。它返回一个整数,表示定时器的编号,这个值能够传递给cancelAnimationFrame用于取消这个函数的执行。

requestID = requestAnimationFrame(callback);

完美兼容

image.png | left | 580x293
若是想要简单的兼容,能够这样子:

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);
        };
    }
}());
相关文章
相关标签/搜索