requestAnimationFrame API – jetzt mit Sub-Millisekunden-Genauigkeit

Ilmari Heikkinen

Wenn Sie requestAnimationFrame genutzt haben, fanden Sie es toll, dass die Farben mit der Aktualisierungsrate des Bildschirms synchronisiert wurden, was zu möglichst High-Fidelity-Animationen führt. Außerdem entlasten Sie die CPU-Lüfter und den Akkuverbrauch, wenn Nutzer zu einem anderen Tab wechseln.

Es wird jedoch eine Änderung an einem Teil der API geben. Der Zeitstempel, der an Ihre Rückruffunktion übergeben wird, ändert sich von einem typischen Date.now()-ähnlichen Zeitstempel zu einer hochauflösenden Messung in Gleitkommamillisekunden seit dem Öffnen der Seite. Wenn Sie diesen Wert verwenden, müssen Sie Ihren Code aktualisieren. Folgen Sie dazu der Anleitung unten.

Nur um sicherzugehen:

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

Wenn Sie den gängigen requestAnimFrame-Shim , der hier bereitgestellt wird, verwenden, wird der Zeitstempelwert nicht verwendet. Sie haben es nicht geklappt. :)

Warum

Warum? Mit rAF erreichen Sie die ultimativen 60 fps, die ideal sind, und 60 fps entsprechen einer Frame-Rate von 16,7 ms pro Frame. Bei der Messung mit ganzen Millisekunden haben wir jedoch eine Genauigkeit von 1/16 für alles, was wir beobachten und auf das wir unsere Anzeigen ausrichten möchten.

Vergleich der Grafiken „16 ms“ und „16 ganze ms“

Wie Sie oben sehen, entspricht der blaue Balken der maximalen Zeit, die Sie für Ihre Arbeit haben, bevor Sie einen neuen Frame zeichnen (bei 60 fps). Sie führen wahrscheinlich mehr als 16 Vorgänge aus, aber mit Ganzzahl-Millisekunden können Sie nur in diesen sehr groben Intervallen planen und messen. Das ist nicht gut genug.

Der Timer mit hoher Auflösung bietet eine wesentlich genauere Angabe:

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

Der Timer mit hoher Auflösung ist derzeit in Chrome als window.performance.webkitNow() verfügbar. Dieser Wert entspricht im Allgemeinen dem neuen Argumentwert, der an den rAF-Callback übergeben wird. Sobald die Spezifikation weiter voranschreitet, wird das Präfix der Methode entfernt und sie ist über performance.now() verfügbar.

Außerdem unterscheiden sich die beiden Werte um mehrere Größenordnungen. performance.now() ist eine Messung in Gleitkommamillisekunden seit Beginn des Ladevorgangs der jeweiligen Seite (genauer gesagt der performance.navigationStart).

Genutzt

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

Die Korrektur ist ganz einfach: Ersetzen Sie startTime und now durch window.performance.now().

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

Dies ist eine ziemlich naive Implementierung, da sie keine Methode mit Präfix now() verwendet und davon ausgeht, dass Date.now() unterstützt wird, was im IE8 nicht der Fall ist.

Funktionserkennung

Wenn Sie das oben beschriebene Muster nicht verwenden und nur wissen möchten, welche Art von Callback-Wert Sie erhalten, können Sie diese Methode verwenden:

requestAnimationFrame(function(timestamp){

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

    // ...

Mit if (timestamp < 1e12) können Sie schnell herausfinden, wie groß die Zahl ist. Technisch gesehen kann es zu einem False Positive kommen, aber nur, wenn eine Webseite 30 Jahre lang kontinuierlich geöffnet ist. Wir können jedoch nicht testen, ob es sich um eine Fließkommazahl handelt, die nicht auf eine Ganzzahl abgerundet wurde. Wenn Sie genügend Timer mit hoher Auflösung anfordern, erhalten Sie irgendwann Ganzzahlwerte.

Wir planen, diese Änderung in Chrome 21 einzuführen. Wenn Sie diesen Rückrufparameter bereits verwenden, aktualisieren Sie Ihren Code.