requestAnimationFrame API - 精确到亚毫秒

Ilmari Heikkinen

如果您一直在使用 requestAnimationFrame,那么您一定会喜欢看到绘制操作与屏幕刷新率同步,从而获得尽可能高保真度的动画。此外,当用户切换到其他标签页时,您还可以减少 CPU 风扇噪音和电池电量消耗。

不过,API 的部分内容即将发生变更。传递给回调函数的时间戳将从典型的 Date.now() 类时间戳更改为自页面打开以来经过的浮点毫秒数的高分辨率测量值。如果您使用此值,则需要根据以下说明更新代码

为了让您更清楚,我说的是:

// assuming requestAnimationFrame method has been normalized for all vendor prefixes..
requestAnimationFrame(function(timestamp){
    // the value of timestamp is changing
});

如果您使用的是此处提供的常见 requestAnimFrame 补丁,则不使用时间戳值。不用担心。:)

原因

为什么呢?好吧,rAF 可帮助您达到理想的 60 fps,而 60 fps 相当于每帧 16.7 毫秒。但使用整数毫秒进行衡量意味着我们想要观察和定位的一切内容的精度为 1/16。

16 毫秒与 16 整数毫秒的图表比较。

如上所示,蓝色条表示您在绘制新帧之前完成所有工作所需的最大时间(以 60fps 为单位)。您可能要执行的操作不止 16 项,但使用整数毫秒时,您只能以这些非常粗略的增量进行调度和衡量。这还不够。

高分辨率计时器通过提供更精确的数据来解决此问题:

Date.now()         //  1337376068250
performance.now()  //  20303.427000007

高分辨率计时器目前在 Chrome 中以 window.performance.webkitNow() 的形式提供,此值通常等于传入 rAF 回调的新参数值。规范在标准中进一步推进后,该方法将舍弃前缀,并通过 performance.now() 提供。

您还会注意到,上述两个值相差几个数量级。performance.now() 是自特定网页开始加载(具体是 performance.navigationStart)以来经过的浮点毫秒数。

正在使用

出现的主要问题是使用此设计模式的动画库:

function MyAnimation(duration) {
    this.startTime = Date.now();
    this.duration = duration;
    requestAnimFrame(this.tick.bind(this));
}
MyAnimation.prototype.tick = function(time) {
    var now = Date.now();
    if (time > now) {
        this.dispatchEvent("ended");
        return;
    }
    ...
    requestAnimFrame(this.tick.bind(this));
}

修复此问题的修改非常简单... 只需扩展 startTimenow 以使用 window.performance.now()

this.startTime = window.performance.now ?
                    (performance.now() + performance.timing.navigationStart) :
                    Date.now();

这是一个相当简单的实现,它不使用带前缀的 now() 方法,并且假定支持 Date.now()(IE8 中不支持)。

功能检测

如果您未使用上述模式,而只想确定您收到的是哪种回调值,可以使用以下方法:

requestAnimationFrame(function(timestamp){

    if (timestamp < 1e12){
        // .. high resolution timer
    } else {
        // integer milliseconds since unix epoch
    }

    // ...

检查 if (timestamp < 1e12) 是一个快速的“鸭子测试”,用于了解我们要处理的数字有多大。从技术上讲,它可能会出现假正例,但只有在网页连续打开 30 年的情况下才会出现。但我们无法测试它是否为浮点数(而非舍入整数)。请求足够的高分辨率计时器,您在某个时间点一定会获得整数值

我们计划在 Chrome 21 中推出此变更,因此如果您已经在利用此回调参数,请务必更新您的代码!