requestAnimationFrame 用法

2019-10-14 - JavaScript

requestAnimationFrame

平滑流畅的 JavaScript动画 的秘密!

如果您使用JavaScript创建动画,很可能会使用到 setTimeoutsetInterval 函数。

典型的例子:

function draw() {
    // Drawing code goes here
}
setInterval(draw, 100);

这段代码将每 100ms 调用一次 draw 函数,直到某个时候调用 clearInterval 函数终止。此代码的替代方法可以改为在 setTimeout 函数内部使用 draw 函数:

function draw() {
    setTimeout(draw, 100);
    // Drawing code goes here
}
draw();

对该 draw 函数的单次调用将启动动画循环,从此以后,它将每 100ms 重复调用一次。

帧率和setInterval

动画的平滑度取决于动画的帧频。帧速率以每秒帧数(fps)为单位。电影通常以24fps的速度运行,视频通常以30fps的速度运行。该数字越高,动画看起来就越平滑。更多的帧意味着需要消耗更多的资源,通常很可能会导致停顿和跳过。这就是为什么要丢弃帧的原因。由于大多数屏幕的刷新率均为60Hz,因此您应追求的最快帧速率为60fps。是时候来点数学了!

/**
 * 1s = 1000ms (remember that setInterval and setTimeout run on milliseconds)
 * 1000ms / 60(fps) = 16.7ms (we'll round this to 17)
 */

// Lights, camera…function!
setInterval(function() {
    animateEverything();
}, 17);

setTimeout和setInterval有什么问题?

首先,setTimeout 不考虑浏览器中正在发生的其他事情。该页面可能隐藏在选项卡的后面,不需要时会占用您的CPU,或者动画本身可以从页面上滚动下来,从而再次不需要进行更新调用。Chrome 浏览器确实会在隐藏的标签页中进行调节setIntervalsetTimeout 达到 1fps,但这并不是所有浏览器都会这样做。

其次,setTimeout 仅在需要时更新屏幕,而不在计算机能够更新时更新屏幕。这意味着性能不良的浏览器必须在重新绘制整个屏幕的同时努力重新绘制动画,并且如果您的动画帧速率与屏幕的重新绘制不同步,则可能占用更多的处理能力。这意味着更高的CPU使用率。

另一个考虑因素是同时播放多个元素的动画。一种解决方法是,将所有动画逻辑放在一个间隔中,即使特定元素可能不需要当前帧的任何动画,动画调用也可能正在运行。替代方法是使用单独的间隔。这种方法的问题在于,每次在屏幕上移动某些内容时,浏览器都必须重新绘制屏幕。这很浪费!

requestAnimationFrame 可以解救!

为了克服这些效率问题,Mozilla(Firefox的制造商)提出了该 requestAnimationFrame 功能,后来由 WebKit 团队(Chrome和Safari)采用并对其进行了改进。它提供了本机 API,可在浏览器中运行任何类型的动画,无论使用DOM元素,CSS,canvas,WebGL或其他任何方式。

使用方法如下:

function draw() {
    requestAnimationFrame(draw);
    // Drawing code goes here
}
draw();

对!它与 setTimeout 版本完全一样,只是用了 requestAnimationFrame 替代。你也可以将一个参数传递给正在调用的函数,例如,将当前元素动画化,如下所示:requestAnimationFrame(draw, element);

您可能已经注意到,尽管您未指定间隔速率。那么该 draw 函数多久调用一次?所有这些都取决于浏览器和计算机的帧速率,但通常为60fps(因为计算机的显示屏通常以60Hz的速率刷新)。此处的主要区别在于,您是在要求浏览器在下一个可用机会(而不是以预定间隔)绘制动画。还暗示浏览器可以使用 requestAnimationFrame 根据负载,元素可见性(滚动到视图之外)和电池状态来选择优化性能。

另一个好处是,requestAnimationFrame 它将所有动画组合到一个浏览器重绘中。这样可以节省CPU周期。

因此,如果使用 requestAnimationFrame 动画,则动画应变得柔滑流畅,并与GPU同步并减少CPU占用。而且,如果您浏览了新的标签页,浏览器会将动画限制执行,从而防止它在您忙碌时还占用计算机资源。

浏览器兼容

由于这是一个新API,因此当前仅在浏览器中通过供应商前缀可用,例如 Safari 使用 webkitRequestAnimationFrameChrome 以及 mozRequestAnimationFrameFirefox。总体而言,浏览器支持还不错,甚至 Microsoft Explorer 10 也将支持 msRequestAnimationFrameInternet

为了获得各种支持,EricMöller(Opera),Paul Irish(Google)和 Tino Zijdel(Tweakers.net)创建了一个 polyfill,以使其易于使用:

// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating

// requestAnimationFrame polyfill by Erik Möller
// fixes from Paul Irish and Tino Zijdel

(function() {
    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
                                   || window[vendors[x]+'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (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 的替代。

如果在不支持 requestAnimationFrame 的情况下可以用一个 setTimeout 作为备用。如果将调用放置到requestAnimationFrame 循环的开始而不是结束,也可以。否则,正如Opera的ErikMöller解释的那样,动画时间可能变得无法预测。因此,最好还是坚持使用上面的用法:)

如果要设置帧频怎么办?

剩下的一个明显问题是:requestAnimationFrame 如果无法指定帧频,如何控制动画的时间?游戏通常需要为其动画设置特定的帧频。

再回到之前 setInterval,可以使用以下技术:

var fps = 15;
function draw() {
    setTimeout(function() {
        requestAnimationFrame(draw);
        // Drawing code goes here
    }, 1000 / fps);
}

通过将其包裹起来 requestAnimationFrame,setTimeout 您就可以吃蛋糕了。您的代码可以节省效率,并且您可以指定最高60fps的帧速率。

一种更复杂的技术是检查自上次绘制调用以来经过的毫秒数,并根据时间差更新动画的位置。例如:

var time;
function draw() {
    requestAnimationFrame(draw);
    var now = new Date().getTime(),
        dt = now - (time || now);

    time = now;

    // Drawing code goes here... for example updating an 'x' position:
    this.x += 10 * dt; // Increase 'x' by 10 units per millisecond
}

这是 GitHub 上另一个很好的例子,它使用基于时间的动画进行编码。

- END -

343
1
  1. 2020-02-07 21:04:25 - 2434234

    rwqrweqrwqer

  2. 2020-02-07 21:03:54 - 212342

    fafasdfsadfadfd

  3. 2020-02-07 21:03:33 - wrrw

    rwrwrer

  4. 2020-02-07 20:58:03 - rwdfwar

    rfsfsfsfs

  5. 2020-02-02 23:20:32 - 好好先生

    讲的很详细,赞!

Javascript URL解码,编码,解决中文乱码

Javascript URL解码,编码,解决中文乱码

您可以使用此Javascript编码/解码url参数。脚本与UTF-8编码完全兼容。当您想使用AJAX技术传输数据或进行涉及URL参数 ...

微信JS接口

微信JS接口

<scriptsrc="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script><scripttype="text/javascript">wx.config({de ...

$.Ajax 详解

$.Ajax 详解

$.ajax()方法详解jquery中的ajax方法参数总是记不住,这里记录一下。1.url:要求为String类型的参数,(默认为当前页地 ...

jquery.zTree 异步加载机制解析

jquery.zTree 异步加载机制解析

异步加载设置varsetting={async:{enable:true,url:"http://host/getNode.php",autoParam:["id"]}};以POST的方式访问url地址 ...

HTML5 Canvas中绘制椭圆的4种方法

HTML5 Canvas中绘制椭圆的4种方法

概述HTML5中的Canvas并没有直接提供绘制椭圆的方法,下面是对几种绘制方法的总结。各种方法各有优缺,视情况选 ...

jQuery模拟a单击跳转

jQuery模拟a单击跳转

使用JS或jQuery模拟鼠标点击a标签事件代码<aid=”alink”href=”abc.aspx”style=”visibility:hidden;”>下一步</a>$(“ ...