requestAnimationFrame API – jetzt mit Sub-Millisekunden-Genauigkeit

Ilmari Heikkinen

Wenn du requestAnimationFrame verwendet hast, hat es dir gefallen, dass sich deine Farben mit der Aktualisierungsrate des Bildschirms synchronisieren und so die High-Fidelity-Animationen ergeben. Außerdem spart Ihre Nutzer CPU-Lüftergeräusche und spart Akkuleistung, wenn sie zu einem anderen Tab wechseln.

In Kürze wird es jedoch eine Änderung an einem Teil der API geben. Der Zeitstempel, der an Ihre Callback-Funktion übergeben wird, ändert sich von einem typischen Date.now()-ähnlichen Zeitstempel zu einem hochauflösenden Messwert von Gleitkomma-Millisekunden seit dem Öffnen der Seite. Wenn Sie diesen Wert verwenden, müssen Sie Ihren Code gemäß der folgenden Erklärung aktualisieren.

Kurz gesagt:

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

Wenn Sie den hier angegebenen gemeinsamen requestAnimFrame-Shim verwenden, verwenden Sie nicht den Zeitstempelwert. Kein Problem. :)

Gute Gründe

Woran liegt das? Die rAF hilft dir dabei, die idealen 60 fps zu erreichen, und 60 fps entsprechen 16,7 ms pro Frame. Bei Messungen mit ganzzahligen Millisekunden haben wir jedoch eine Precision von 1/16 für alles, was wir beobachten und ausrichten möchten.

Diagrammvergleich von 16 ms vs. 16 ganzzahliger ms

Wie Sie oben sehen können, gibt der blaue Balken die maximale Zeit an, die Sie für Ihre Arbeit haben, bevor Sie einen neuen Frame zeichnen (mit 60 fps). Wahrscheinlich machen Sie mehr als 16 Dinge, aber mit ganzzahligen Millisekunden können Sie die Planung und Messung nur in sehr kleinteiligen Schritten durchführen. Das ist nicht gut genug.

Der hochauflösende Timer liefert eine weitaus genauere Zahl, um dieses Problem zu lösen:

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

Der hochauflösende Timer ist in Chrome derzeit als window.performance.webkitNow() verfügbar. Dieser Wert entspricht in der Regel dem neuen Argumentwert, der an den rAF-Callback übergeben wird. Sobald die Spezifikation die Standards fortsetzt, löscht die Methode das Präfix und ist über performance.now() verfügbar.

Sie werden außerdem feststellen, dass die beiden obigen Werte um viele Größenordnungen unterschiedlich sind. performance.now() gibt den Gleitkommawert in Millisekunden seit Beginn des Ladens der jeweiligen Seite an (performance.navigationStart, um spezifisch zu sein).

Genutzt

Das Hauptproblem beim Zuschneiden sind Animationsbibliotheken, die dieses Designmuster verwenden:

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));
}

Eine Änderung dieses Problems ist ziemlich einfach: Erweitern Sie startTime und now, um window.performance.now() zu verwenden.

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

Diese Implementierung ist ziemlich einfach. Es wird keine Methode mit dem Präfix now() verwendet. Außerdem wird vorausgesetzt, dass Date.now() unterstützt wird, was in IE8 nicht vorhanden ist.

Funktionserkennung

Wenn Sie das obige Muster nicht verwenden und nur herausfinden möchten, welche Art von Callback-Wert Sie erhalten, können Sie dieses Verfahren verwenden:

requestAnimationFrame(function(timestamp){

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

    // ...

Mit if (timestamp < 1e12) zu prüfen, ist ein kurzer Test, um herauszufinden, wie groß die Zahl ist, mit der wir es zu tun haben. Technisch gesehen könnte dies jedoch nur dann falsch-positiv sein, wenn eine Webseite 30 Jahre lang ununterbrochen geöffnet ist. Wir können jedoch nicht testen, ob es sich um eine Gleitkommazahl handelt (anstatt auf eine Ganzzahl festzulegen). Wenn Sie nach genügend hochauflösenden Timern fragen, erhalten Sie irgendwann Ganzzahlwerte.

Wir planen, diese Änderung in Chrome 21 bereitzustellen. Wenn ihr diesen Callback-Parameter bereits nutzt, solltet ihr euren Code aktualisieren.