Mit Canvas in die Zukunft der Plug-in-freien multimedialen Websites
Kommentare

Transformationen
Nun mag man diese Vorgehensweise in Frage stellen, wenn es lediglich darum ginge, zwei Farbwerte in der Rückhand zu behalten. Wie bereits erwähnt, bezieht sich die Sicherung allerdings

Transformationen

Nun mag man diese Vorgehensweise in Frage stellen, wenn es lediglich darum ginge, zwei Farbwerte in der Rückhand zu behalten. Wie bereits erwähnt, bezieht sich die Sicherung allerdings auf die gesamte Konfiguration. Dazu zählen unter anderem auch durchgeführte Transformationen.

Da Canvas, wie bekannt, keine Objekte pro gezeichneter Form erzeugt, werden Manipulationen auf dem Context selbst vorgenommen und hinterlegt. Um ein Rechteck beispielsweise in einem Winkel von 45 Grad gedreht dazustellen, muss man dem Kontext vor der Zeichenoperation durch context.fillRect dazu auffordern (Listing 5).

Listing 5
 (...)
var PI_DIV_180 = Math.PI / 180,
    degree     = 45,
    radians    = degree * PI_DIV_180;

context.save();
context.translate(100, 100);
context.rotate(radians);
context.scale(0.5);

context.fillStyle = '#ff00ff';
context.fillRect(0, 0, 100, 100);
context.restore();

(...)

Transformationen sind ein starkes Werkzeug, wenn man komfortabel und ohne viel mathematisches Hintergrundwissen Verschiebungen, Drehungen oder Skalierungen beim Zeichnen vornehmen möchte. Hierzu stehen einem die entsprechenden Befehle context.translate(x, y), context.rotate(radians) und context.scale(scaleX, scaleY) zur Verfügung. translate sorgt dafür, dass jede Koordinate, die den Context passiert, um X und Y verschoben wird. scale kann zwei Skalierungsfaktoren handhaben: Ist nur der erste Parameter angegeben, gilt die Skalierung für X- und Y-Achse gleichermaßen. Es handelt sich hierbei um Float-Werte wobei ‚1.0‘ der Standardskalierung entspricht. Ein context.scale(0.5) entspricht der Verkleinerung auf die Hälfte der ursprünglichen Größe, context.scale(2.0) vergrößert auf das Doppelte des Ursprungs.

Zu context.rotate(radians) sollte man Folgendes wissen: Wie auch die Winkelfunktionen aus JavaScript Math (und in nahezu jeder anderen Programmiersprache), erwartet context.rotate die Winkelmaße als Bogenmaß (auch Radians genannt) [6]. Durch den alltäglichen Umgang mit Gradwinkelmaßen (45, 90, 180, 360), ist es für die meisten eher ungewohnt, in Bogenmaß (0.25π, 0.5π, π, 2π) zu denken. Daher lauert hier eine kleine Stolperfalle, die immer wieder gerne zu kurzzeitiger Verwirrung führt.

Dabei können Grad sehr gut in Winkelmaß umgerechnet werden. Ohne viel auf mathematische Hintergründe einzugehen, entspricht ein Grad exakt (Math.PI / 180) Bogenmaß. Somit lässt sich sehr einfach via degrees * (Math.PI / 180) dennoch Gradwinkel als Parameter für rotate nutzen. Das kommt einem unter anderem in einer Animation zugute. Hier erhöht man in der Regel bestimmte Werte in einem proportinalen Verhältnis (z. B. winkel++). Den Gradwinkel um eine natürliche Zahl zu verändern, fühlt sich allemal besser an und ist vermutlich auch direkter zu mappen als ein Bogenmaßwinkel um ein Vielfaches von (Math.PI / 180).

Animation

Zunächst sollte man sich bewusst werden, warum Animationen im Allgemeinen funktionieren. Das menschliche Auge ist verhältnismäßig träge. Bereits ab einer niedrigen Anzahl Bildwechsel in der Sekunde wird der eigentliche Wechsel nicht mehr wahrgenommen und als fließende Bewegung interpretiert [7]. Ein Daumenkino macht sich genau diese Gesetzmäßigkeit zu Nutzen. Genauso ist es auch bei der Animation auf Computern. Canvas ist so implementiert, dass es sehr gut darin ist, häufig wechselnde Bilddaten zu visualisieren. Als Faustregel gilt: Eine Aktualisierungsrate zwischen etwa 35 Frames per Second (FPS) bis 60 FPS reicht für die meisten Animationen aus (Listing 6).

Listing 6
var desiredFrames = 35,
    secondInMilis = 1000,
    interval      = secondInMillis / desiredFrames;

function draw() {
  // (...)
}

setInterval(function() {
  draw();
}, interval);

Entkoppelung ist hierbei äußerst wichtig, da das Drawing andernfalls den restlichen Programmablauf blockieren würde. setInterval(callback, interval) ist der denkbar naivste Ansatz zur entkoppelten Wiederholung. Denn setInterval läuft selbst dann weiter, wenn die Canvas-Animation gerade nicht sichtbar ist. Die Folgen sind unnütze Belastung der CPU und hochtourig drehende Lüfter. Notebook-User können sich somit ganz besonders darüber freuen, wenn eine minimierte Website ihnen dadurch zwar einen warmen Schoß bereitet, aber gleichzeitig auch die Akkuladung raubt.

Hier unterstützen moderne Browser ein ganz besonderes Feature: requestAnimationFrame(callback(time)) [8] sorgt dafür, dass der im Callback enthaltene Code nur dann ausgeführt wird, wenn das HTML-Dokument auch wirklich sichtbar ist.

Der Callback von setInterval wird stumpf nach einer bestimmten Zeitangabe neu getriggert. Daher ist es sehr wahrscheinlich, dass mehr gezeichnet wird als eigentlich dargestellt werden kann. Sprich, wenn der Browser oder das Endgerät weniger Frames pro Sekunde zeichnet als setInterval dazu auffordert, bleiben etliche Drawing Cycles ungenutzt.

Da requestAnimationFrame in der Regel mit dem Drawing Cycle der Browseranwendung gekoppelt ist, entfällt der beschriebene Nachteil. Im Gegenteil, an einigen Stellen profitiert man sogar davon.

Laut Spezifikation kann die Callback-Funktion, die an requestAnimationFrame übergeben wird, einen Parameter timestamp erhalten. Hierbei handelt es sich um einen Timestamp analog zu dem aus Date.now(). Wenn man es in seiner Animation berücksichtigt, kann man anhand der vergangenen Zeit die Werte und Positionen einer Animation manipulieren. Das ergibt vor allen Dingen bei instabilen oder zu hohen Aktualisierungsraten einen Sinn. In bestimmten Fällen – wie vielleicht bei Spielen – nimmt man es in Kauf, dass ein Spiel zeitweise sprunghaft oder ruckelig läuft, aber innerhalb des Spieluniversums mit der gewünschten Taktung und Geschwindigkeit synchron bleibt. Ansonsten bewegen sich alle Elemente nur so schnell, wie requestAnimationFrame gerade zu aktualisieren in der Lage ist. Da requestAnimationFrame ausserdem kein Drawing aufruft, wenn das HTML-Dokument gerade nicht sichtbar ist, kann es nützlich sein, nach Wiederanzeige eine Information darüber zu erhalten, um damit umzugehen (Listing 7).

Listing 7
function draw(timestamp) {
  var totalTime = timestamp - startTime();

  setTimeout(function(){
    requestAnimationFrame(draw);
  }, 1000 / 5);
}

var startTime = Date.now();
requestAnimationFrame(draw);

// Alternativ:
//   webkitRequestAnimationFrame(draw);
//   mozRequestAnimationFrame(draw);
//   oRequestAnimationFrame(draw);
//   msRequestAnimationFrame(draw);

Wie man es von vielen neu eingeführten CSS-Attributen kennt, ist auch requestAnimationFrame in den meisten Browsern mit einem Prefix versehen – und in älteren Browsern gar nicht vorhanden. Da JavaScript allerdings eine nahezu vollflexible Sprache ist, hat die Webcommunity auch hier schnell Wege gefunden ein entsprechendes Workaround zu entwickeln [9].

Optimierungen

Je mehr Frames pro Sekunde dargestellt werden können, desto flüssiger und realistischer kann eine Animation wirken. Hierbei ist jedoch zu beachten, dass jeder Frame zunächst durch das Programm berechnet und gezeichnet werden muss. Canvas 2-D als Software-Rendering-orientiertes API stößt dabei schnell an eine Performanceobergrenze. Das Ergebnis sind ruckelnde oder langsam ablaufende Animationen. Viele performancebedingte Probleme lassen sich durch ein paar Kniffe schnell eindämmen.

Ein wesentlich wichtiger Punkt, den man sich merken sollte wenn man mit Canvas arbeitet, ist: Canvas-Koordinaten sind Integer oder, passender formuliert, ganze Zahlen! Obwohl das Drawing API auch Fließkommazahlen als gültige Werte entgegennimmt, merkt man spätestens beim Zeichnen, dass sich hier einige Frames gewinnen lassen. Sprich, vor jedem Zeichnen sollten die Koordinaten und Längenangaben zu ganzen Zahlen gewandelt werden. Positiver Nebeneffekt: Die Kanten der gezeichneten Elemente bleiben scharf [10].

Dieser Ansatz ergibt einen Sinn, wenn man sich erneut vor Augen hält, dass das Drawing API auf einem auf einem Pixel-Puffer, also einem Array, arbeitet. Arrays kennen keine Float Indicies. Also interpoliert hier der Browser bzw. das 2-D API. Das führt zu einem markanten Performanceverlust. Laut aktueller Benchmarks scheint dies insbesondere dem Browser Chrome nicht mehr viel auszumachen. Mozilla-Browser hingegen merken den Unterschied deutlich [11].

Sei es Fluch oder Segen, bewegen sich viele der Animationen und Spiele, die mit dem Canvas 2-D API umgesetzt werden können, auf einem technologischen Niveau, das dem gegen Mitte und Ende der 90er Jahre nahe kommt. Dadurch hat man unter anderem den Vorteil, aus den Erfahrungen der Grafikprogrammierung der 90er Jahre zu lernen. Also Zeiten, in denen Entwickler Animationen auch ohne zusätzliche 3-D-Hardware im Softwaremodus geschrieben haben.

Bitshifting mit Zahlenwerten ist dabei beispielsweise eine wesentliche Methode gewesen, um bestimmte Rechenoperationen sehr performant durchführen zu können [12]. Während Douglas Crockford in „JavaScript: The Good Parts“, aus Performancegründen davon abrät, Bitshifting zu nutzen [13], erzielen Bitwise Operations in aktuellen Benchmarks dennoch sehr gute Ergebnisse [14].

Auf Plattformen, auf denen Speicher kein Problem darstellt, kann man außerdem den Trade-off von Speicher gegen Performance eingehen. Wenn klar ist, dass sich bestimmte Werte (Koordinaten, Farben, Faktoren) niemals einen bestimmten Wertebereich verlassen werden, ist es oftmals sinnvoll, diese Wertepalette in einem Lookup-Table aufzubereiten. Farbpaletten von Plasmaeffekten oder ähnliche Animationen, die auf Winkelfunktion setzen, eigenen sich oftmals gut dazu.

Fazit

Canvas ist eine prima Sache! Im Grunde genommen ist es an der Zeit, umzudenken. Viele Methoden und Lösungen, die Entwickler über die Jahre zusammengetragen haben, sind Workarounds, um mit HTML Anforderungen umzusetzen, für die HTML weder gedacht noch geeignet sind.

Wer es gewohnt ist, wie bei ActionScript/Flash, mit vorgefertigten DisplayObjects und in einer Hierarchie zu arbeiten, sollte sich zuvor eine Bibliothek suchen, die diese Features bietet. Hardwarebeschleunigtes, dreidimensionales Rendering ist dank WebGL stark auf dem Vormarsch und Bibliotheken wie ThreeJS [15] ebnen bereits im Voraus den Weg.

Bei der Unterstützung und Brauchbarkeit von Canvas haben die WebKit-basierten Browser stark vorgelegt, was nicht zuletzt auch an einer sehr performanten Implementierung der JavaScript-Engines liegt.

Auf mobilen Endgeräten lässt Canvas allerdings noch sehr zu wünschen übrig. Wer hier mehr vor hat, als ein paar selten aktualisierte Inhalte zeichnen zu lassen, wird mit Canvas nicht viel Spaß haben. Für flüssig dargestellte Bewegungen, soweit das Endgerät dies leisten kann, sind CSS3-Transitions hier in der Regel der bessere Weg. Gerade WebKit-Browser und der Mobile Safari bringen eine sehr gute Unterstützung für Animations, Transitions und Transformations mit sich. Wer es benötigt, auch im dreidimensionalen Raum.

Canvas hat bereits jetzt die Entwicklung Plug-in-freier Multimediawebsites stark beeinflussen können. Die kommenden Jahre bleiben spannend!

Dennis Wilson ist selbstständiger Softwareentwickler und Berater für Web-, Mobile- und Interactive-Appliations (http://www.abakia.de/). Seine Schwerpunkte sind vor allem die Arbeit mit interaktiver Computergrafik, User Interfaces und clientseitiger Technologien allgemein.
Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -