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 は、フレームあたり 16.7 ms に相当します。ただし、ミリ秒の整数で測定すると、測定とターゲティングの対象となるすべての値で 1/16 の精度が得られます。

16 ミリ秒と 16 整数ミリ秒のグラフの比較。

上記の青いバーは、新しいフレームをペイントするまでにすべての処理を完了する必要がある最大時間を示しています(60 fps)。おそらく 16 件を超える処理を行っているでしょうが、整数ミリ秒では、非常に大きな間隔でスケジュールと測定を行うしかありません。もう一歩でした。

高解像度タイマーは、はるかに正確な数値を提供することで、この問題を解決します。

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() メソッドを使用していません。また、Date.now() のサポートを前提としていますが、IE8 ではサポートされていません。

特徴検出

上記のパターンを使用しておらず、取得したコールバック値の種類を特定するだけの場合は、次の手法を使用できます。

requestAnimationFrame(function(timestamp){

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

    // ...

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

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