requestAnimationFrame && setTimeout 原理剖析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了requestAnimationFrame && setTimeout 原理剖析相关的知识,希望对你有一定的参考价值。

参考技术A

  眼睛的另一个重要特是视觉惰,即光象一旦在视网膜上形成,视觉将会对这个光象的感觉维持一个有限的时间,这种生理现象叫做 视觉暂留 。对于中等亮度的光刺激,视觉暂留时间约为 50ms 至 200ms 。当我们看屏幕的时候,虽然你什么也没做,但是屏幕还是以特定的频率在不停刷新,只是这个刷新过程我们肉眼识别到他的细微变化,这就是我们接下来要说的 屏幕刷新频率

  我们日常的显示器,一般频率在 60Hz 左右,意味着我们的屏幕每 1 秒需要刷新 60 次,也就是说每 1000ms 需要更新 60 次的屏幕图像,那么我们由此可以得出,屏幕图像更新一次所需要的时间间隔也就是 16.7ms(1000/60≈16.7) 。
  由于人的眼睛具有 视觉暂留效应 ,且暂留时间为 50ms 至 200ms ,也就是说人在看屏幕的时候,还没等到你的大脑印象消失,电脑屏幕就已经更新了,所以这个间隔让你感觉不到变化。
  那么屏幕刷新频率是不是越大越好?我们可以大胆假设一下,假如我有三个显示器,刷新频率分别为 1Hz 、 60Hz 、 200Hz 、那么对应的更新周期时间分别为 1000ms 、 16.7ms 、 5ms 。 也就是频率越大,图像更新的间隔就越短,我们看到的画面就会越稳定, 当达到一秒更新一次的时候,这个时候我们就能够感觉到明显的屏幕闪烁,带来视觉疲劳。

   setTimeout 说白了就是个延时计时器,通过设置固定的时间间隔,从而时间一到,执行相应的回调方法。这个过程不会考虑屏幕刷新频率,换句话讲,它的时间是写死的。那么为什么会存在丢帧现象发生,通俗来说就是为什么使用 setTimeout 我会感觉到卡顿,画面不稳定。

  1. setTimeout 执行的时间与屏幕的刷新频率不一致会导致丢帧现象。我们不考虑异步问题,假设我们现在的屏幕设备是 60Hz 的刷新频率。那么我们图像的更新周期也就是 16.7ms ,我现在的动画要求是每 10ms 往下偏移 1px ,那么这个丢帧现象是如何产生的?这里我通过一张图来解释一下。

   所以根据分析结果以及实验效果,如果 setTimeout 执行的顺序与屏幕的刷新频率不一致,会造成丢帧现象,从而视觉上带给我们的就是不流畅

  2.由于 javascript 属于 单线程 ,而 setTimeout 任务会被放入异步队列,通俗来讲就是它的执行得等一等,具体等什么,不知道,就是想再等等。只有主线程的任务执行完毕之后,才会轮到它去执行,也就是说我虽然设置 setTimeout 16.7ms 间隔去执行动画属性改变,但是实际运行的时间可能会有所延迟。这个延迟可能会导致执行的时间与屏幕刷新的时间串掉,造成丢帧。

   官方解释 : window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。也就是说 requestAnimationFrame 它不需要你去手动设置执行间隔时间,它是跟随系统的屏幕刷新频率走的,如果屏幕刷新频率是 60Hz ,那么它的执行间隔就是 16.7ms(1000/60≈16.7) ,如果屏幕刷新频率是 100Hz ,那么它的执行间隔就是 10ms(1000/100=10) ,这样就能够保证它的执行与屏幕的刷新频率保持一致,从而避免丢帧现象。
  为了提高性能和电池寿命,因此在大多数浏览器里,当 requestAnimationFrame() 运行在后台标签页或者隐藏的 <iframe> 里时, requestAnimationFrame() 会被暂停调用以提升性能和电池寿命。

  源地址: https://github.com/darius/requestAnimationFrame

requestAnimationFrame 使用

1、概述

参考网址:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame

window.requestAnimationFrame() 参数是一个回调函数。

回调的次数建议每秒60次。

这个回调函数只有一个传参,DOMHighResTimeStamp,指示requestAnimationFrame() 开始触发回调函数的当前时间(performance.now() 返回的时间)。

与setTimeout相比,requestAnimationFrame最大的优势是由系统来决定回调函数的执行时机。具体一点讲,如果屏幕刷新率是60Hz,那么回调函数就每16.7ms被执行一次,如果刷新率是75Hz,那么这个时间间隔就变成了1000/75=13.3ms,换句话说就是,requestAnimationFrame的步伐跟着系统的刷新步伐走。

 

2、示例

<!DOCTYPE html>
<html lang="zh">

    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>requestAnimationFrame使用</title>
        <style type="text/css">
            *{
                padding: 0;
                margin: 0;
            }
            #ani{
                width: 100px;
                height: 100px;
                border: 1px solid greenyellow;
            }
        </style>
    </head>

    <body>
        <div id="ani">

        </div>
        <script type="text/javascript">
            var start = null;
            var element = document.getElementById(ani);
            element.style.position = absolute;
            function step(timestamp) {
                if(!start) start = timestamp;
                var progress = timestamp - start;
                element.style.left = Math.min(progress / 10, 400) + px;
                //动画执行时间
                if(progress < 4000) {
                    //在动画没有结束前,递归渲染
                    window.requestAnimationFrame(step);
                }
            }
            //第一帧渲染
            window.requestAnimationFrame(step);
        </script>
    </body>

</html>

优点:

  • CPU节能:使用setTimeout实现的动画,当页面被隐藏或最小化时,setTimeout 仍然在后台执行动画任务,由于此时页面处于不可见或不可用状态,刷新动画是没有意义的,完全是浪费CPU资源。而requestAnimationFrame则完全不同,当页面处理未激活的状态下,该页面的屏幕刷新任务也会被系统暂停,因此跟着系统步伐走的requestAnimationFrame也会停止渲染,当页面被激活时,动画就从上次停留的地方继续执行,有效节省了CPU开销。
  • 函数节流:在高频率事件(resize,scroll等)中,为了防止在一个刷新间隔内发生多次函数执行,使用requestAnimationFrame可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销。一个刷新间隔内函数执行多次时没有意义的,因为显示器每16.7ms刷新一次,多次绘制并不会在屏幕上体现出来。

以上是关于requestAnimationFrame && setTimeout 原理剖析的主要内容,如果未能解决你的问题,请参考以下文章

requestAnimationFrame.js后续学习

requestAnimationFrame兼容性写法

requestAnimationFrame (待整理)

requestAnimationFrame 方法你真的用对了吗?

详解Web API requestAnimationFrame

requestAnimationFrame