requestAnimationFrame API – jetzt mit Sub-Millisekunden-Genauigkeit

Ilmari Heikkinen

Wenn Sie requestAnimationFrame verwendet haben, haben Sie vielleicht schon bemerkt, dass Ihre Zeichnungen mit der Bildwiederholrate des Bildschirms synchronisiert wurden, was zu möglichst realistischen Animationen führt. Außerdem sparen Sie den Nutzern beim Wechseln zu einem anderen Tab den CPU-Lüfterlärm und Akkuverbrauch.

Es steht jedoch eine Änderung an einem Teil der API bevor. 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 sind in Sicherheit. :)

Warum

Warum? Mit rAF kannst du die ultimativen 60 fps erreichen, was ideal ist.60 fps entsprechen 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 Hochauflösende Timer 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 in der Regel 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.