requestAnimationFrame – Ablauf
Die Syntax von requestAnimationFrame () gleicht der Syntax von setTimeout (), da requestAnimationFrame wiederholt aufgerufen wird. Nur ein Timer oder Interval wird nicht benötigt.
function animate () { … req = requestAnimationFrame (animate); } let req = requestAnimationFrame (animate); cancel.addEventListener ("click", () => { cancelAnimationFrame (req); });
setTimeout und setInterval sind altgediente Techniken in Animationen mit Javascript. Sie verändern das animierte Element alle t Millisekunden. Aber diese alten Methoden sind ineffizient. Wenn der Benutzer ein anderes Tab im Browser öffnet, läuft die Animation weiter. requestAnimationFrame ist effizienter.
Wenn der Benutzer zu einem anderen Tab wechselt, kann der Browser die Animation pausieren, um die CPU weniger zu belasten. Schön für unsere mobilen Geräte …
Frame-Rate – Einzelbilder pro Sekunde
Bei Animationen mit setTimeout oder setInterval kann das Script die aktuelle Frequenz der Frames nicht feststellen. requestAnimationFrame wird per Vorgabe 60 mal pro Sekunde aufgerufen – mehr ist selten nötig (oder besser: möglich), denn 60 Hz ist die Refresh-Rate der meisten Browser (oder Zeitrahmen von 16.7ms).
In Animationen mit requestAnimationFrame kann die Browser die Refresh-Rate reduzieren, wenn die Animation in einem Hintergrund-Tab läuft, um den Energieverbrauch zu senken. Statt einen Timeout mit einer Verzögerung von 16.7ms in einer Schleife immer wieder aufzurufen, feuern die Browser ein Event, sobald das System wieder einen Frame rendern kann.
requestAnimationFrame zur Unterstützung von CSS-Animationen
CSS-Animationen sind mächtig, aber ihnen fehlen die Ereignisse, die eine Aktion starten oder beenden: CSS erkennt zwar ein Hovern mit der Maus, aber nicht das Touch des Fingers und Mausklicks nur auf wenigen Elementen (wie Radiobuttons). Auch bei komplexen Ketten (z.B. »starte Aktion, wenn eine andere Aktion abgelaufen ist«) bleibt CSS außen vor. Für Aktionen wie Stop, Pause und Resume eignet eine Kombination von CSS @keyframes und requestAnimationFrame allerdings sehr gut.
Die Toastscheiben springen mit einfachen @keyframes-Animationen ein erstes Mal automatisch mit dem Laden der Seite. Der Button für eine erneute Animation bleibt verborgen, bis die CSS-@keyframes-Animation abgelaufen ist, und startet eine neue Toast-Session.
#toast1 { animation: spring 3s 1s } #toast2 { animation: spring 4s } @keyframes spring { 0% { transform: translateY(0px)} 30% { transform: translateY(-180px)} 31% { transform: translateY(-180px)} 100% { transform: translateY(-180px)} } #press { animation: toastit 1s 3.5s forwards; opacity: 0; } @keyframes toastit { 100% { opacity: 1 } }
const toaster = document.querySelector ("#press"); toaster.addEventListener ("click", () => { const toast1 = document.querySelector ("#toast1"); toast1.style.animationName = "none"; requestAnimationFrame(() => { toast1.style.animationName = ""; }); const toast2 = document.querySelector ("#toast2"); toast2.style.animationName = "none"; requestAnimationFrame(() => { toast2.style.animationName = ""; }); toaster.style.animationName = "none"; requestAnimationFrame(() => { toaster.style.animationName = ""; }); });
toaster.style.animationName = "none" löst die @keyframes-Animation vom Element. Sofort wird der animationName wieder gelöscht, die ursprüngliche @keyframes-Animation ist damit wieder verbunden.
Für komplexe Animationen haben wir heute auch das Web Animation Api, das eine CSS-ähnliche Notation nutzt, und den Start einer Aktion abhängig vom Zustand anderer Aktionen macht. Dennoch bleibt requestAnimationFrame die Wahl bei canvas-Animationen, denn im canvas-Element herrscht nicht das Document Object Model, sondern canvas basiert auf Pixeln.
Syntax requestAnimationFrame
Wenn der Screen neu gerendert werden soll, wird requestAnimationFrame mit dem Name der Funktion aufgerufen, in der die Veränderungen und Berechnungen durchgeführt werden (aka Callback).
<div class="flex stopper"> <img id="rad" src="rad.svg" width="200" height="200"> <img id="eck" src="eck.svg" width="40" height="40"> </div> <div id="lucky"></div> <button id="drehen">DREHEN!</button> <button id="stopp" disabled="disabled">STOPP</button>
const obj = { on: document.querySelector ("#drehen"), off: document.querySelector ("#stopp"), rad: document.querySelector ("#rad"), lucky: document.querySelector ("#lucky"), deg: 0, repeater: null }
Alle globalen Variablen der Animation in ein Objekt gesteckt, um nicht mit anderen Variablen zu kollidieren. deg steht für die Umdrehung des Rads in Grad, repeater ist die Referenz auf requestAnimationFrame (), um die Animation zu beenden.
function turn () { obj.deg++; if (obj.deg > 359) obj.deg = 0; obj.rad.style.transform = "rotate(" + obj.deg + "deg)"; obj.repeater = requestAnimationFrame (turn); } obj.on.addEventListener ("click", function () { obj.off.removeAttribute ("disabled"); obj.on.setAttribute ("disabled", "true"); obj.repeater = requestAnimationFrame (turn); });
requestAnimationFrame ist keine Schleife und kein Timer, sondern die Funktion turn () muss jedes Mal aufgerufen werden, wenn die Animation etwas ändert.
Der Klick auf DREHEN weckt das Zahlenrad. Der Button DREHEN wird deaktiviert, damit er nicht während der Animation ein weiteres Mal geklickt werden kann. Das disabled-Attribut des STOPP-Buttons kann weg, damit das Rad jederzeit angehalten werden kann.
STOPP beendet die Animation mit cancelAnimationFrame(obj.repeater). Jetzt nur noch mit den aktuellen Wert von deg berechnen, welche Zahl unter dem Dreieck liegt.
obj.off.addEventListener ("click", function () {
cancelAnimationFrame(obj.repeater);
obj.on.removeAttribute ("disabled")
switch (true) {
case (obj.deg < 45):
obj.lucky.textContent = "5";
break;
…
}
});
Das Manko der Javascript-Animationen
Javascript hat kein Easing von Haus aus, wie wir es von CSS-Transitions und -Animationen kennen. Kein langsames Anlaufen oder Losschießen, kein weiches oder abruptes Ausbremsen. Diese Animationen wirken wie ein Metronom: Tick, Tack, Tick, Tack. Erst jQuery hat diese Mechanismen, die für natürliche Bewegungsabläufe sorgen, eingebracht. Das Easing in jQuery beruht auf den Algorithmen von Robert Penner, die es auf Github für Javascript gibt.
Wer also auf requestAnimationFrame für Animationen – z.B. für Canvas-Animationen – setzt, sollte sich Improved Easing Functions auf Josh On Design ansehen. Umgesetzt auf Introduction to Easing in JavaScript für Javascript.
requestAnimationFrame mit Parametern
request Animation Frame wird mit einer Callback-Funktion aufgerufen. Wenn Parameter übergeben werden, kann die Callback Function auch eine anonyme Funktion sein, die wiederum die benötigten Parameter enthält.
function moveCurtain (curt, height) { easing = easeInOutQuart(step, height, -300, 90); step++; if (easing < 1) { cancelAnimationFrame (repeater); step = 0; } else { curt.setAttribute('height',easing); repeater = requestAnimationFrame (function () { moveCurtain (curt, easing); }); } }
function.bind() ist ein weiterer Mechanismus, um requestAnimationFrame mit Parametern aufzurufen. bind erzeugt eine neue Funktion, die this als ersten Parameter an bind() übergibt.
function move ( elem, timestamp) { // Berechne left und y elem.setAttribute("transform", "translate(" + left + " " + y +")"); requestAnimationFrame (move.bind (0,elem)); }; for (let i=0; i<waves.length; i++) { requestAnimationFrame(move.bind (0, waves[i])); }
Javascript animate () – Web Animation API
Mit animate () ist neben setTimeout, setInterval und requestAnimationFrame eine weitere Technik für Animationen auf den Plan getreten. Das Web Animation API mit animate() lehnt sich an CSS-Keyframes-Animationen an und eignet sich besonders für die Ablaufsteuerung von komplexen Objekten. requestAnimationFrame bleibt weiterhin die Basis für Canvas-Animationen.