Sehr tolle Erklärung in deutsch MediaEvent
Bei Selfhtml Wiki wird der Schwerpunkt auf den Timestamp und Zeitpunkt Berechnungen gelegt.
Siehe auch die alternative Möglichkeit Web Animation Api um CSS Eigenschaften zu
animieren.
Hat man mehrere Anweisungen, die in einer oder mehreren Zeitschleifen ausgeführt werden und zusätzlich noch weitere Events durch Usereingaben, könnte das zu Verzögerungen führen, da die CPU überlastet wird durch das ständige Rendern der Ansicht.
requestAnimationFrame
liefert die Lösung, denn das Render- Intervall wird vom Browser gesteuert. Hiermit wird alles gleichzeitig und so effizient wie möglich gerendert. Außerdem werden die Aktualisierung in einem anderen Tab-Browserfenster gestoppt.Bei setInterval oder setTimeout laufen die Animationen in allen Tabs des Browsers weiter.
<input name="meintextfeld" type="text" id="meintextfeld" class="textfeld">
var i=1; function zahlen() { i++; document.getElementById("meintextfeld").value = i; } function renderScene(){ requestAnimationFrame(renderScene); zahlen(); } renderScene();
Beispiel / Farbanination Beispiel / Camel Beispiel (weitere Camel Beispiele hier)
Pendelbewegung In meinen alten Animate Tipps (ehemals Flash Actionscript3) finden sich viele Beispiele für programmierte Bewegungen und animierte Linien, die man leicht auf CSS oder Canvas übertragen kann.
Im Beispiel wird der Wert multi bei jedem Frame um 0.03 erhöht. Durch Math.sin()
wird damit ein Wert übergeben der zwischen -1 und +1 , in einer Sinuskurve hoch und runter geht.
rota = Math.sin(multi += 0.03) * 90; pendel.style.transform = "rotate(" + rota + "deg)";
Kreisbewegung Beispiel Sinus und Cosinus kann man auch dazu einsetzen eine Kreisbewegung mit x- und y-Koordinate zu programmieren, da Cosinus entgegengesetzt zu Sinus verläuft.
i wird ständig erhöht. Der Wert bestimmt die Drehgeschwindigkeit. Zum Radius wird ein Versatzwert angegeben, der den Mittelpunkt der Drehung auf den Mittelpunkt der Seite legt. Der Wert -50 wird eingesetzt, da sonst der Drehpunkt des Bildes in der linken oberen Ecke des Bildes liegen würde.
i += 0.01; posY = Math.sin(i) * radius + window.innerHeight/2 -50; posX = Math.cos(i) * radius + window.innerWidth/2 -50; moon.style.left = posX + "px"; moon.style.top = posY + "px";
Kreisbewegung mit Timestamp Klickt man im obigen Beisiel mehrmals auf den Startbutton, wird die Drehbewegung schneller, weil der Wert von i doppelt so schnell ansteigt. Setzt man für i den Bruchteil des Timestamp ein, geschieht das nicht, weil der Timestamp in allen requestAnimationFrames gleich ist. i = timestamp / 2000;
mehr dazu unten.
Flieger In diesem Beispiel bewegt sich der Flieger von rechts nach links, solange seine Position größer ist als -250, anderfalls wird er an den rechten Rand gesetzt. Die vertikale Position yPos wird mit Math.sin() berechnet, anhand eines Wertes der sich ständig erhöht, dem Timestamp. Sinus liefert immer eine Zahl zwischen -1 und 1. Multipliziert man den Wert bekommt man eine schöne Sinuskurvenbewegung.
i = timestamp / 2000; posY = Math.sin(i) * radius + 200; if (posX < -250) { posX = window.innerWidth + 20; } else { posX -= 2; } flyer.style.left = posX + "px"; flyer.style.top = posY + "px";
Will man requestAnimationFrame
starten und stoppen, muss man es einer Variablen übergeben.
requestId = window.requestAnimationFrame(loop);
Dann kann man sie folgendermaßen über die Variable löschen:
window.cancelAnimationFrame(requestId);
Im folgenden habe ich mehrere Beispiele erstellt, die Schritt für Schritt immer komplexer werden. Bitte Quellcode untersuchen
Beispiel 1, Beispiel 2, Beispiel 3, Beispiel 4, Beispiel 5, Beispiel 6
Im folgenden gibt es einen Button der zwischen starten und stoppen hin und her schaltet. Das geschieht mit Hilfe der Variablen requestId die mit undefined
intialisiert wird. In der Funktion startStop() wird in
einer if Struktur diese Variable abgefragt. Auch wenn undefined
kein boolscher Wert ist, so liefert undefined
doch den Wert false
. siehe dazu Boolscher Datentyp Casting
Durch das Ausrufezeichen am Anfang wird der Wert false
in true
umgekehrt und somit wird requestId das Objekt requestAnimationFrame(renderScene)
zugwiesen. Dadurch wird renderScene() in Schleife aufgerufen.
Klickt man das nächste mal auf den Button liefert requestId in der if Struktur true
und somit wird unter else
, die Animation gestoppt und requestId wieder auf undefined
gesetzt. F12 console.log(Boolean(requesId));
Weist man der Funktion, die mit requestAnimationFrame aufgerufen wird, einen Parameter zu, so liefert dieser einen Timestamp.
<input type="btn" value="Start Stop"> <div id="output"></div>
var btn = document.getElementById("btn"); var output = document.getElementById("output"); btn.addEventListener("click", startStop); var requestId = undefined; function startStop(){ if (!requestId) { requestId = window.requestAnimationFrame(renderScene); btn.textContent = "Stop"; }else{ window.cancelAnimationFrame(requestId); requestId = undefined; btn.textContent = "Start"; } } function renderScene(time){ requestId = window.requestAnimationFrame(renderScene); output.innerHTML = time; }
Beispiel 1 Timestamp Schrittdauer
Der Parameter den man der Callback-Funktion die mit requestAnimationFrame aufgerufen wird übergibt liefert einen timestamp. Das ist die Zeit in Millisekunden, die seit einem Bezugszeitpunkt (z.B. dem Laden der Seite) vergangen ist. Mit diesem Timestamp kann man die Zeit berechnen, die seit dem Start der Callback Funktion vergangen ist, oder man ermittelt die Zeit zwischen den einzelnen Aufrufen oder Frames.
Im folgendem Beispiel wird in der Variablen startZeit der Startzeitpunkt hinterlegt, wenn die Callback-Funktion animate() gestartet wird. Wenn requestAnimationFrame(animate)
gestoppt wird, wird auch startZeit wieder auf undefined
gesetzt, so dass beim erneuten Starten startZeit wieder den neuen Wert bekommt. Diese startZeit wird von dem aktuellen timestamp subtrahiert und man erhält quasi die aktuelle Zeit oder
Dauer der Schleife.
Die Variable vorigeZeit bekommt in jedem Frame den aktuellen timestamp. Bevor man im nächsten Frame den Wert wieder neu zuweist, hat man den Timestamp des vorigen Frames. Die Subtraktion vom aktuellen Timestamp ergibt die Zeit zwischen
2 Frames timestamp - vorigeZeit
Weil der Timestamp gebraucht wird, wird animate() nicht direkt über den Startbutton aufgerufen.
HTML
<button id="startBtn">Start</button> <button id="stopBtn">Stop</button> <p id="output"></p>
Javascript
var startBtn = document.getElementById("startBtn"); var stopBtn = document.getElementById("stopBtn"); var output = document.getElementById("output"); var requestId; var startZeit = undefined; var vorigeZeit = undefined; startBtn.addEventListener('click', startAnimation); stopBtn.addEventListener('click', stopAnimation); function animate(timestamp) { if (startZeit === undefined) { // beim ersten Aufruf ist startZeit undefined startZeit = timestamp; // Dann timestamp als Startzeit speichern } var schrittDauer = timestamp - vorigeZeit; //vorigeZeit ist timestamp des vorigen Frames var gesamtZeit = Math.floor(timestamp - startZeit); //gesamtZeit seit Start vorigeZeit = timestamp; //wird beim nächsten Aufruf gebraucht. requestID = requestAnimationFrame(animate); output.innerHTML = schrittDauer+"<br>"+gesamtZeit; } function startAnimation(){ requestID = window.requestAnimationFrame(animate); } function stopAnimation(){ if (requestID) { cancelAnimationFrame(requestID); } requestID = undefined; startZeit = undefined; }
Wenn man requestAnimationFrame als Methode in einem Objekt nutzen will, muss man eine Lösung finden auf this
zuzugreifen.
In folgenden Beispielen erstelle ich ein Objekt, welches von Image
erbt und mit requestAnimationFrame
animiert wird. Es ist ähnlich meiner Car Animationen.
Hier noch ein weiteres Beispiels welches eine Abwandlung der Serie benutzerdefiniertes HTML Element ist