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 shim を使用している場合は、タイムスタンプ値は使用されません。お疲れさまでした。:)

理由

その理由は、rAF は理想的な究極の 60 fps を得るのに役立ち、60 fps は 1 フレームあたり 16.7 ms に相当します。しかし、整数ミリ秒で測定すると、監視およびターゲットとするすべてのものに対して 1/16 の精度になります。

16 ms と 16 整数 ms のグラフの比較。

上記のように、青色のバーは、新しいフレームを描画する前にすべての作業を行う必要がある最大時間(60 fps)を表します。おそらく 16 個以上の処理を行うことになりますが、integer ミリ秒を使用すれば、非常に大きな単位をスケジュールして測定することしかできません。もう一歩でした。

高解像度タイマーが、より正確な数値を提供することでこれを解決します。

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

現在、Chrome では高解像度タイマーを window.performance.webkitNow() として利用できます。この値は通常、rAF コールバックに渡される新しい引数の値と同じです。仕様がさらに標準化されると、メソッドはプレフィックスを削除し、performance.now() を通じて使用できるようになります。

また、上記の 2 つの値は桁数に大きく異なります。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() メソッドを使用せず、IE8 にはない Date.now() のサポートも想定しています。

特徴検出

上記のパターンを使用せず、どのようなコールバック値を取得するかを確認するには、次の方法を使用します。

requestAnimationFrame(function(timestamp){

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

    // ...

if (timestamp < 1e12) チェックは、処理している数値の大きさを確認する簡単なダックテストです。技術的には誤検出の可能性がありますが、ウェブページが 30 年間続いている場合に限ります。ただし、(整数に切り捨てられていない)浮動小数点数であるかどうかをテストすることはできません。十分な数の高解像度タイマーを要求すれば、どこかの時点で整数値を取得する義務があることになります。

この変更は Chrome 21 で展開される予定です。すでにこのコールバック パラメータを使用している場合は、コードを更新してください。