JavaScript/Window/requestAnimationFrame

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Die Methode window.requestAnimationFrame ermöglicht es Funktionen – ähnlich wie mit setTimeout – zeitlich verzögert auszuführen, bietet aber einige Vorteile.

Syntax

var requestID = window.requestAnimationFrame(callback);

  • callback: ein Funktionsobjekt, das vor dem nächsten Rendern ausgeführt wird
    callback muss ein Funktionsobjekt sein, also ohne Anführungszeichen und Klammern. Sie können keine eigenen Parameter für diese Funktion bereitstellen.
  • requestID: Rückgabewert, der z. B. bei cancelAnimationframe(requestID) benötigt wird

Aufruf der Callback-Funktion

Wenn Sie requestAnimationFrame aufrufen, wird Ihre Callback-Funktion in einer Liste offener Animationframe-Anforderungen hinterlegt. Sie bekommt dort eine Referenznummer, die requestID.

Sobald der Browser entscheidet, dass es an der Zeit für einen neuen Animationszyklus ist (was typischerweise von der Bildwiederholrate Ihrer Grafikkarte abhängt), ermittelt er die Zeit in Millisekunden, die seit einem Bezugszeitpunkt (z.B. dem Laden der Seite) vergangen sind. Diese Zeit sollte laut Spezifikation eigentlich auf 5µs genau sein, allerdings reduzieren die Browser diese Genauigkeit mittlerweile künstlich, weil ein bösartiges JavaScript auf diese Weise Rückschlüsse auf das Timingverhalten des Computers ziehen kann, was verschiedenste Hackerangriffe ermöglicht. Alles, wovon Sie ausgehen können, ist, dass Sie eine einigermaßen genaue Zeitangabe erhalten und damit ermitteln können, wieviel Zeit zwischen zwei Callback-Aufrufen vergangen ist.

Diese Information ist für eine glatte Animation wichtig, denn Sie können nicht davon ausgehen, für jedes Bild, das der Monitor anzeigt, einen neuen Animationsschritt liefern zu können. Je nachdem, was Ihre Webseite sonst noch zu erledigen hat, kann der Aufruf der Callback-Funktionen sich etwas verschieben oder Anzeigeframes übersprungen werden. An Hand des vom Browser gelieferten Timestamps müssen Sie nun in Ihrem Callback berechnen, wieviel Zeit vergangen ist und welche Bewegung die von Ihnen animierten Dinge in dieser Zeit ausgeführt haben. Das Ergebnis ist die neue Position - und eventuell auch Ausrichtung - der angezeigten Grafiken. Um es darzustellen, können Sie entweder HTML- und SVG-Elemente umpositionieren oder transformieren, oder sie verwenden einen Canvas und zeichnen die Szene komplett neu (2D Canvas).

Es ist übrigens möglich, mehrere Animationen parallel laufen zu lassen. Man kann mit requestAnimationFrame mehrere Callbacks in die erwähnte Liste offener Animationframe-Anforderungen eintragen, und wenn ein neuer Animationsschritt erfolgt, werden sie alle nacheinander, aber mit dem gleichen Timestamp-Wert, ausgeführt.

Anwendungsbeispiel

Beispiel ansehen …
  const startBtn  = document.getElementById('startBtn'),
        stopBtn  = document.getElementById('stopBtn'),
        resetBtn = document.getElementById('resetBtn'),
        elem     = document.getElementById("anim");

  let requestID = undefined,
      startZeit = undefined,
      vorigeZeit = undefined;
 
    function render(zeitpunkt, schrittDauer) {
        // Zeit in ms auf Wert von 0-1599 umrechnen. Werte > 800 an 800 spiegeln
        // (durch Subtraktion von 1600), dadurch läuft die Kugel hin und her.
        let x = (Math.floor(zeitpunkt / 2) % 1600);
        if (x > 800)
            x = 1600 - x;
        elem.style.left = x + "px";
    }
	
    function animate(timestamp) {
        if (startZeit === undefined) {    // Voriger Zeitpunkt unbekannt? 
            startZeit = timestamp;        // Dann timestamp als Startzeit und vorige Zeit speichern
            vorigeZeit = timestamp;
        }

        // Szene für den Animationszeitpunkt neu zeichnen
        render(timestamp - startZeit, timestamp - vorigeZeit);
        vorigeZeit = timestamp;

        requestID = requestAnimationFrame(animate);  // Nächsten Frame anfordern.
    }
	
    function stopAnimation () {
        if (requestID) {
            cancelAnimationFrame(requestID);
        } 
        requestID = undefined;
        startZeit = undefined;
    }
	 
    startBtn.addEventListener('click',function () { 
        // Hier nicht animate direkt aufrufen, weil der Animations-Timestamp gebraucht wird!
        requestID = window.requestAnimationFrame(animate);
    });
    stopBtn.addEventListener('click', stopAnimation);
    resetBtn.addEventListener('click',function () {
        stopAnimation();
        elem.style.left = "0px";
    });

Durch einen Klick auf den Start-Button wird der erste Animationframe angefordert und die Funktion animate() als Callback übergeben. Der Rückgabewert ist die erste Request-ID.

Die animate()-Funktion kümmert sich um die Zeitsteuerung der Animation. Beim ersten Aufruf ist noch kein Startzeitpunkt bekannt und die startZeit-Variable enthält noch undefined. Das wird zum Anlass genommen, den übergebenen Timestamp als Bezugszeitpunkt für die Animation zu setzen. Mit diesem Bezugszeitpunkt wird nun die Funktion render() aufgerufen, die die Aufgabe hat, die Szene zu einem bestimmten Zeitpunkt darzustellen. Außerdem wird noch die Zeit übergeben, die seit dem letzten Animationsschritt vergangen ist. Danach fordert animate den nächsten Animationsframe an und gibt sich selbst als Callback mit. Bei einer typischen Bildwiederholrate von 60 Bildern pro Sekunde ist also nun zu erwarten, dass render sechzig mal pro Sekunde aufgerufen wird.

Die render()-Funktion muss sich nicht mehr um die Details der Zeitpunktermittlung kümmern, und sich auch nicht merken, wie lange der letzte Schritt her ist. Sie bekommt beides übergeben. Da die hier gezeigte Animation sehr einfach ist, genügt der aktuelle Zeitpunkt und die vergangene Zeit wird nicht benötigt. Der Zeitpunkt wird ihn in eine X-Koordinate umgerechnet. Division des Zeitpunktes durch 2 bedeutet, dass nach 3200ms der Wert 1600 herauskommt. Die nachfolgende Modulo-Berechnung (Rest der Division durch 1600) bewirkt, dass die ermittelten Werte immer die Skala von 0 bis 1600 durchlaufen. In einem zweiten Schritt wird noch für x-Werte über 800 die Richtung umgekehrt. Die Subtraktion 1600-x bewirkt, dass die Werte von 800-1600 in einen rückwärts laufenden Wert von 800 bis 0 umgerechnet werden. Der Effekt ist, dass das animierte Element alle 3,2 Sekunden von links nach rechts und wieder zurück läuft.

Beachten Sie: In einer interaktiven Animation (z.B. ein Spiel) kommen Sie nicht so einfach davon. Dort müssen Sie die Benutzeraktionen in Betracht ziehen und auch die vergangene Zeit berücksichtigen, um zu bestimmen, um wie viele Pixel sich das Horn des Monsters auf den Helden zubewegt hat.

cancelAnimationFrame

Bei einem Klick auf den Stop-Button wird die Funktion stopAnimation() aufgerufen. Sie beendet die Animation mit window.cancelAnimationFrame, wobei die RequestID als Parameter benötigt wird.

Syntax

window.cancelAnimationFrame(requestID);

Fallback für ältere Browser

Sehr alte Browser (vor Internet Explorer 10) kennen requestAnimationFrame noch nicht. Sie können sich mit diesem Polyfill behelfen:

Beispiel
(function() {
    var lastTime = 0;

    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) {
            var currTime = Date.now();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 
              timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };
 
    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
}());

In dieser anonymen Funktion wird überprüft, ob requestAnimationFrame bekannt ist. Falls nicht, wird setTimeout aufgerufen, was zwar einige Nachteile hat, aber in älteren Browsern funktioniert.

Vorteile gegenüber setTimeout

requestAnimationFrame ist besonders für Animationen geeignet, da es dafür ausgelegt ist, so schnell wie möglich, aber nicht schneller als nötig ausgeführt zu werden. Das heißt, es muss keine Verzögerungen angegeben werden, wann die Funktion ausgeführt werden soll. Der Browser führt sie aus, sobald das Bild auf dem Monitor aktualisiert werden soll. Innerhalb der Funktion muss requestAnimationFrame erneut aufgerufen werden, um die Animation fortzusetzen.

Der Browser sorgt dafür, dass die Funktion nicht öfter aufgerufen wird, als der Bildschirm das Bild neu zeichnen kann (üblicherweise 60 bis 75 Bilder pro Sekunde). Auch auf inaktiven (und daher nicht sichtbaren) Tabs wird maximal ein AnimationFrame pro Sekunde ausgelöst, um nicht unnötig Rechenkraft zu verschwenden. Sollte das angegebene Element nicht sichtbar sein (weil z. B. aus dem sichtbaren Bereich gescrollt) wird die Aktualisierungsrate ebenfalls heruntergesetzt.

Empfehlung: Für die Animation von CSS-Eigenschaften können Sie die Web Animations API verwenden, die diese Animationen schnell und einfach nativ im Browser durchführt.

Weblinks