JavaScript/Canvas/Animation

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

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.

Anwendungsbeispiele

Beispiel ansehen …
  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);
In diesem Beispiel wird von der Funktion draw() eine einfache Canvas-Zeichnung erstellt: Ein kleines Quadrat wird an der Position (x,y) gezeichnet. Diese Funktion wird aus der Funktion animate() heraus aufgerufen und mit der Zeichenposition versorgt. Die animate-Funktion wird einmalig aus dem Hauptprogramm heraus als Animations-Callback an requestAnimationFrame übergeben und fordert dann den nächsten Frame für sich automatisch neu an.

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.

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:


Demos: