Als die Bilder laufen lernten

Auf unserem Canvas können wir Bilder rendern. Wir können externe Bilder einfügen, Bilder aus dem DOM nehmen oder unser Canvas selber bemalen. Daraus nun ein Video bzw. eine Animation zu erstellen ist äußerst einfach. Denn ein Video stellt nichts anderes dar, als sich verändernde Bilder, die in einer bestimmten Zeit schnell hintereinander angezeigt werden.

Wenn wir einen Film im Fernsehen schauen, dann haben wir eine flüssige Animation vor uns. Würden wir den Fernseher allerdings aufnehmen und den Streifen in Slow-Motion (also wesentlich langsamer als das Original) abspielen, stellen wir fest, das wir am Ende einzelne Bilder heraus bekommen.

Das wir Menschen die Bilder jedoch in Originalfassung nicht einzeln sondern als Film sehen, liegt an unserem Gehirn. Dieses kann zwar mehrere Bilder pro Sekunde wahrnehmen, jedoch nur bis zu einer gewissen Anzahl einzeln deuten. Sobald wir mehr als 25 Bilder in der Sekunde sehen, macht unser Gehirn daraus eine Animation, indem er die wichtigsten Merkmale (Bewegungen von Schauspielern, Farbveränderungen, etc.) herausnimmt und daraus einen Film im Kopf abspielen lässt. Das muss der Filter in unserem Gehirn auch so machen, denn alles andere würde in starken Kopfschmerzen bzw. einem Überfluss an Informationen resultieren.

Das Prinzip ist also klar: Wir müssen lediglich 25 Bilder pro Sekunde abspielen und haben eine Animation. Das ganze wurde zu einer Einheit zusammengefasst: Frames Per Second (FPS). Bei der Mindestanzahl an Bildern (25) pro Sekunde haben wir allerdings ein nicht sehr flüssiges Bild, weswegen mehr Bilder auch eine schönere Animation erzeugen.

Schritt für Schritt

Um eine einfache Animation im Canvas zu erzeugen, benötigen wir 4 einfache Schritte:

  1. Canvas säubern
    Da wir unsere Objekte mindestens 25 Mal die Sekunde rendern, müssen wir vor jedem Durchgang unser Canvas von den vorherigen Darstellungen befreien. Andernfalls würde unsere Leinwand binnen Sekunden voll sein. Mit der Methode clearRect() erreichen wir den erwünschten Effekt.
  2. Zustand speichern
    Für den Fall, dass wir Eigenschaften ändern und Veränderungen an unseren Objekten vornehmen (Transformationen, Style-Eigenschaft ändern), müssen wir vorher den Zustand speichern, wenn wir bei jedem neuen Durchgang mit gleichen Einstellungen arbeiten wollen. Mit der Methode save() erreichen wir den erwünschten Effekt.
  3. Ausgabe rendern
    Nun können wir unsere einzelnen Bilder pro Sekunde rendern. Dafür haben wir mehrere Möglichkeiten: setTimeout(), setInterval(), requestAnimationFrame().
  4. Zustand wiederherstellen
    Als letztes stellen wir unseren Ausgangs-Zustand her, damit wir beim nächsten Durchgang unsere gewohnten Einstellungen wieder haben. Mit der Methode restore() erreichen wir den erwünschten Effekt.

Eine einfache Animation

Das Herzstück einer Animation ist zweifelsfrei die wiederholte Ausführung von Code. Mit Javascript haben wir dafür folgende Möglichkeiten:

FunktionBeschreibung
setTimeout( f, t )Führt die Funktion f aus, nachdem die Zeit t verstrichen ist. Die Zeit wird in Millisekunden angegeben.
setInterval( f, t )Führt die Funktion f jedes Mal aus, wenn die Zeit t verstrichen ist. Die Zeit wird in Millisekunden angegeben.
requestAnimationFrame( c )Teilt dem Browser mit, dass eine Animation geplant ist und lässt diesen die Funktion (Callback) c ausführen. Die Funktion c muss einen erneuten und damit rekursiven Aufruf der Funktion requestAnimationFrame(c) aufweisen.

Mit der Funktion setTimeout( f, t ) können wir eine Funktion, die wir im ersten Parameter angeben, nach einer bestimmten Zeit ausführen. Diese Zeit wird in Millisekunden gemessen und im zweiten Paramter angegeben.

  • Html
  • Javascript
  • Ergebnis
<html> <head></head> <body> <canvas id="canvas_id" width="600" height="200" style="border: 1px solid red;"></canvas> </body> </html>
var Canvas = document.getElementById( "canvas_id" ); var Context = Canvas.getContext( "2d" ); //Vordefinierte Funktion function ZeichneQuad(){ Context.fillRect( 100, 10, 50, 50 ); } setTimeout( ZeichneQuad, 2000 ); // 2 Sekunden //Anonyme Funktion setTimeout( function() { Context.fillRect( 10, 10, 50, 50 ); // 1 Sekunden }, 1000 );

Das obige Beispiel zeigt die Verwendung der Methode setTimeout. Diese wartet 1 Sekunde und führt dann die Anweisungen der anonymen Funktion (ohne Name) aus. Nach der zweiten Sekunde wird die Anweisung der Funktion ZeichneRechteck ausgeführt. Wichtig ist darauf zu achten, dass die Reihenfolge der setTimeout() Funktionen keine Rolle spielt. Das Quadrat an der Stelle 10/10 wird als erstes gerendert, obwohl die zugehörige Funktion im Programmablauf als letztes auftaucht.

Mit der Funktion setInterval( f, t ) können wir eine Funktion, die wir im ersten Parameter angeben, jedes Mal ausführen, wenn eine bestimmte Zeit verstrichen ist. Diese Zeit wird in Millisekunden gemessen und im zweiten Parameter angegeben.

  • Html
  • Javascript
  • Ergebnis
<html> <head></head> <body> <canvas id="canvas_id" width="600" height="200" style="border: 1px solid red;"></canvas> </body> </html>
var Canvas = document.getElementById( "canvas_id" ); var Context = Canvas.getContext( "2d" ); //Jede Sekunde ein Quadrat zeichnen, bis die Anzahl 4 erreicht ist var Anzahl = 4; var Counter = 0; var IntervalId = setInterval( function() { Context.fillRect( 100 * Counter, 10, 50, 50 ); Counter++; //Counter um 1 erhöhen //Interval löschen, wenn die Anzahl erreicht ist if( Counter >= 4 ) clearInterval( IntervalId ); }, 1000 );

Im obigen Beispiel haben wir die Funktion setInterval() für unsere Animation benutzt. Die Funktion haben wir zwar anonym übergeben, hätten jedoch auch eine benannte nehmen können. Der entscheidende Unterschied zur Funktion setTimeout() ist klar ersichtlich. Die angegebene Funktion wird im Interval so oft ausgeführt, bis eine Abbruch-Bedingung eintritt. In unserem Fall haben wir geprüft, ob 4 Quadrate gezeichnet wurden. Wenn dem so ist, wird die Funktion clearInterval( id ) ausgeführt, welche als Paramter die Interval-Id annimmt. Die Interval-Id ist eine Zahl, die von der Funktion setInterval() zurückgegeben wird. Diese haben wir in der Variablen IntervalId gespeichert, um sie der clearInterval() Funktion zu übergeben.

Die Funktion requestAnimationFrame(c) hat gegenüber den obigen Funktionen folgende Vorteil:

  • Der Browser kann die Animation optimieren, weswegen diese oftmals flüssiger wirkt.
  • Wenn der Browser Tab inaktiv ist, wird die Animation gestoppt. Das spart Prozessor-, Graphikkarten- und Speicher-Leistung. Dadurch wird ebenfalls die Akku-Laufzeit erhöht.

Für eine flüssige Animation brauchen wir 60 Bilder die Sekunde; also 60 FPS. Jetzt könnten wir versuchen die Funktion setInterval( f, 1000/60 ) auszuführen. 1000 Millisekunden stellen 1 Sekunde dar. Teilen wir diese durch 60, haben wir unsere 60 Ausführungen in der Sekunde. Könnte man denken, stimmt aber nicht.

Die Funktionen setTimeout() und setInterval() parsen die Millisekunden zu einer Ganzzahl, weswegen wir entweder 16ms oder 17ms als Ergebnis bekommen. Wir benötigen jedoch 16.67ms. Aus diesem Grund bekommen wir hin und wieder einen Ruck in der Animation.

Mit requestAnimationFrame() ist dies nicht der Fall. Sobald wir diese Funktion ausführen, wird der User-Agent des Browser in Kenntnis gesetzt, dass wir eine Animation mithilfe eines Skripts starten wollen.

  • Html
  • Javascript
  • Ergebnis
<html> <head></head> <body> <canvas id="canvas_id" width="600" height="200" style="border: 1px solid red;"></canvas> </body> </html>
var Canvas = document.getElementById( "canvas_id" ); var Context = Canvas.getContext( "2d" ); var Grad = 0; var AnimationsId; function ZeichneQuad() { Context.save(); Context.clearRect(0, 0, Canvas.width, Canvas.height ); Context.translate( Canvas.width / 2, Canvas.height / 2); Context.rotate( (Math.PI/180) * Grad ); Context.fillRect( 50 / -2, 50 / -2, 50, 50 ); Grad++; Context.restore(); //Bei jedem Durchgang ein Aufruf AnimationsId = window.requestAnimationFrame( ZeichneQuad ); } //Das erste Mal den Browser in Kenntnis setzen window.requestAnimationFrame( ZeichneQuad ); setTimeout( function(){ window.cancelAnimationFrame(AnimationsId) }, 2000 );

Im obigen Beispiel haben wir die Funktion ZeichneQuad() definiert, welche ein Quadrat in der Mitte des Canvas rotieren lässt. Das wichtigste an dieser Funktion ist der Aufruf von requestAnimationFrame(), wobei wir als Parameter erneut die Funktion ZeichneQuad() übergeben. Damit wird bei jedem Durchgang der Browser erneut darauf aufmerksam gemacht, dass wir eine weitere Zeichnung vornehmen. Da wir unsere Funktion nun definiert haben, müssen wir den Stein ins Rollen bringen und als Anstoß die Funktion requestAnimationFrame( ZeichneQuad ) einmalig Ausführen. Nach 2 Sekunden wird die Funktion cancelAnimationFrame( id ) mit der AnimationsId aufgerufen, die wir bei jedem Aufruf von requestAnimationFrame() als Rückgabewert bekommen. Die Animation wird dadurch gestoppt.

Nun gibt es auch hier wieder unterschiedliche Implementationen der verschiedenen Browser, d.h. wir benötigen unterschiedliche Funktionsnamen.

  • Html
  • Javascript
  • Ergebnis
<html> <head></head> <body> <canvas id="canvas_id" width="600" height="200" style="border: 1px solid red;"></canvas> </body> </html>
var Canvas = document.getElementById( "canvas_id" ); var Context = Canvas.getContext( "2d" ); window.requestAnimFrame = ( function( c ) { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function( c ) { window.setTimeout( c, 1000 / 60 ); }; })(); var Grad = 0; function ZeichneQuad() { Context.save(); Context.clearRect(0, 0, Canvas.width, Canvas.height ); Context.translate( Canvas.width / 2, Canvas.height / 2); Context.rotate( (Math.PI/180) * Grad ); Context.fillRect( 50 / -2, 50 / -2, 50, 50 ); Grad++; Context.restore(); requestAnimFrame( ZeichneQuad ); } requestAnimFrame( ZeichneQuad );

Der neue Code im obigen Beispiel definiert eine neue Methode für das Objekt window mit einem Parameter c für unsere Zeichenfunktion. Geprüft wird mit einer Verkettung von logischen Oder-Operatoren (||), ob die jeweiligen Funktionen vorhanden sind. Gibt es diese nicht, wird eine anonyme Funktion als letzte Möglichkeit zurückgegeben, die 60 Mal in der Sekunde sich selbst aufruft.

Dass ganze stellt eine Rückversicherung dar, für Browser, die unter anderem requestAnimationFrame() nicht aktiviert bzw. implementiert haben.

MethodeBrowser
requestAnimationFrameStandard Funktionsname.
webkitRequestAnimationFrameBrowser der Webkit-Engine (Chrome, Opera, Safari)
mozRequestAnimationFrameMozilla Firefox
oRequestAnimationFrameÄltere Opera Versionen
msRequestAnimationFrameMicrosoft Internet Explorer