JavaScript/Canvas/Animation
HTML5 Canvas eignet sich besonders gut für die Animation von beweglichen Objekten. Allerdings kann eine mit canvas gezeichnete Grafik nicht bewegt oder verändert werden. Dafür muss bei jedem Einzelschritt die aktuelle Grafik gelöscht und dann neu gezeichnet werden. Da es erst bei einer Bildwechselfrequenz von über 20 Bildern pro Sekunde zu ruckelfreien Darstellungen kommt (Fernsehen und Computerspiele haben sogar eine Bildfrequenz von 50 – 60 Hz), kommt hier dem Prozessor des Computers eine entscheidende Rolle zu.
Für die Animation von Scripten stehen folgende Methoden zur Verfügung:
- setTimeout: führt eine Funktion nach einer Verzögerung (Timoeout) aus.
- setInterval: setzt ein Intervall, das eine Funktion immer wieder ausführt.
- requestAnimationFrame: bittet den Browser eine Animation immer wieder auszuführen
Gegenüber den beiden oberen Methoden hat die requestAnimationFrame
-Methode des window-Objekts einige Vorteile:
Bei einem Aufruf von requestAnimationFrame
wird als Parameter die zu animierende Funktion übergeben.
Dabei werden alle Einzelschritte immer wieder neu gezeichnet und dann durch den neuen Frame überschrieben.
Der Browser signalisiert, wann der nächste Schritt gezeichnet werden soll und kann dabei auf hardwarebeschleunigte Routinen zurückgreifen sowie das Rendern anderer Objekte mit der Animation koordinieren.
In inaktiven Tabs wird die Animation verlangsamt bzw. unterbrochen, was Prozessorleistung und damit auch Strom spart und sich damit bei mobilen Geräten positiv auf die Laufzeit auswirkt.
Inhaltsverzeichnis
Anwendungsbeispiele
let canvas = document.getElementById('canvas'),
x = 0, y = 15, q = 25,
xMax = canvas.clientWidth - q,
speed = xMax / 1000,
lastTimestamp = -1;
function animate(timestamp) {
// Ab dem zweiten Aufruf beginnt die Animation, beim ersten Aufruf
// wird an der Startposition gezeichnet.
if (lastTimestamp > 0) {
// Neue X-Position berechnen
let delta_x = (timestamp - lastTimestamp ) * speed;
x += delta_x;
// Gültigen X-Bereich verlassen? Wenden
if(x < 0) {
reverse(0);
}
if (x > xMax) {
reverse(xMax);
}
}
// Quadrat an der neuen Position zeichnen
draw(x, y, q);
// Zeitpunkt merken um im nächsten Schritt die Zeitdifferenz zu bestimmen
lastTimestamp = timestamp;
// Aufruf für den nächsten Animationsframe registrieren
requestAnimationFrame(animate);
}
function reverse(mirrorX) {
x = 2*mirrorX - x;
speed = -speed;
}
function draw(squareX, squareY, squareSize) {
let context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
context.fillStyle = '#dfac20';
context.fillRect(x, y, squareSize, squareSize);
context.lineWidth = 3;
context.strokeStyle = '#3983ab';
context.strokeRect(x, y, squareSize, squareSize);
}
requestAnimationFrame(animate);
Ausblick
Die oben aufgeführten Beispiele zeigen nur den prinizpiellen Aufbau von Animationen. Häufig werden aber nicht nur lineare Bewegungen animiert, sondern auch komplexere Abläufe, die…
- Einflüsse der Schwerkraft beim Werfen und Fallen
- Kollisionserkennung
- Lichtverhältnisse mit Beleuchtung und Schatten
berücksichtigen.
All dies kann mit JavaScript-Scripten berechnet werden, für räumliche Animationen gibt es mit WebGL eine aufregende Alternative.
ruckelnde Animationen
Da bei jedem Schritt der Animation das gesamte Bild neu gezeichnet werden muss, empfiehlt es sich bei komplizierteren Hintergründen zwei Leinwände zu verwenden:
- Der erste Canvas zeichnet einmal den Hintergrund
- Das zu animierende Objekt wird in einem zweiten canvas über den ersten gelegt und immer wieder neu gezeichnet
Siehe auch:
Weblinks
Tutorials:
- MDN: Basic Animations
- MDN: Advanced animations
- MDN: Efficient animation for web games
Demos:
Es gibt ein paar übergreifende Variablen. x und y für die Position des Quadrats, q für seine Kantenlänge und speed für die Geschwindigkeit in Pixeln pro Sekunde, mit der es sich bewegen soll. Aus der Breite des canvas und q wird der maximal zulässige Wert für x berechnet und in xMax gespeichert. Und in lastTimestamp kann sich die animate-Funktion merken, wann sie das letzte Mal aufgerufen wurde.
Da requestAnimationFrame jeweils die Laufzeit der Seite in Millisekunden übergeben bekommt, kann animate pro Aufruf die Zeit berechnen, die seit dem letzten Aufruf vergangen ist - außer beim ersten Mal. Hier ist der gemerkte Zeitpunkt des letzten Aufrufs noch -1. Dies wird einfach als Nullpunkt betrachtet und das Quadrat an der Startposition gezeichnet. Ab dem zweiten Aufruf wird die Zeitdifferenz bestimmt und mit Hilfe der Variablen
speed
in eine Entfernung umgerechnet. Ein speed-Wert vn xMax / 1000 bedeutet: in 1000 Millisekunden einmal über den Canvas. Die Bewegungsgeschwindigkeit ist so von der Frequenz der animate-Aufrufe völlig unabhängig.Wird durch X die Canvas-Grenze überschritten, wird die Bewegungsrichtung umgedreht (speed = -speed) und der Bewegungsüberschuss in die Gegenrichtung umgedreht. Dafür muss der "zu weit gelaufene" X-Wert vom doppelten der Begrenzungskoordinate abgezogen werden. Warum? Wenn g die Grenzposition ist, dann ist der Abstand zu dem Punkt, bis zu dem wir zu weit gelaufen sind, d=(x-g). Wir möchten auf die andere Seite von g, und zwar mit dem gleichen Abstand. Dafür müssen auf g den negativen Wert von d addieren, bzw. d von g abziehen. Das korrigierte x berechnet sich also durch g - (x-g) = 2*g - x. Das funktioniert am linken wie am rechten Rand - machen Sie sich ein paar Beispiele und fürchten Sie keine negativen Zahlen!
Die Korrektur und Bewegungsumkehr wird von der Funktion reverse übernommen, die die Grenzposition als Parameter übergeben bekommt.
Nach Berechnen des neuen x-Wertes und der Korrektur, falls der Rand überschritten wurde, kann das Quadrat an der berechneten (x,y)-Position gezeichnet werden und der Aufruf von animate kann für den nächsten AnimationFrame angefordert werden.