Gefühlte 90% aller HTML5-Demos im Web verwenden das Canvas-Element. Das machen sie auch nicht ohne Grund, denn das Element und seine APIs ermöglichen etwas Spektakuläres, das vorher in Browsern nicht möglich war: das Zeichnen und Animieren beliebiger Formen und Farben, bis auf den letzten Pixel kontrollierbar.

Das Canvas-Element eröffnet ganz neue Möglichkeiten, die weit über das über die bunten Kästchen hinausgehen, die man allein CSS und JavaScript durch den Browser schubsen kann, von statischen Diagrammen bis zum 3D-Shooter kann man alles rendern, was das Herz begehrt. Beispiele gefällig?

Bei so vielen Möglichkeiten versteht es sich von selbst, dass das Element über eine nicht gerade kleine Sammlung von API-Methoden verfügt, die man nicht mal eben zwischendurch auswendig lernen kann. Hinzu kommt, dass die API auf recht niedrigem Level ansetzt und nur die Funktionalität bietet, die es für jeden Anwendungsfall bieten muss.

So gibt es etwa keine Funktion, die einen Kreis zeichnen kann, denn nicht jede denkbare Canvas-Anwendung braucht so etwas – möchte man eine Kreisfunktion haben, muss man sie sich selbst schreiben. In den meisten Fällen wird man als Webentwickler nicht mit den normalen Canvas-Funktionen arbeiten, sondern mit Frameworks, die auf die Standardfunktionen aufsetzen.

Die größten Herausforderung liegen in den Besonderheiten von Pixelgrafik-Animationen und der händischen Programmierung dieser. Das Implementieren von Framebuffern oder Framerate-Limitern war bisher nicht täglich Brot eines Webentwicklers, aber wer mit dem Canvas-Element flüssige Animationen erschaffen möchte, kommt um diese Sorte fremdartiger Probleme nicht herum. Es gibt also viel neues zu lernen – packen wir es an!

So tickt das Canvas-Element

Es liegt in der Natur der Sache, dass wir im Rahmen dieses kleinen Artikels nicht den vollen Umfang der Canvas-API besprechen können. Selbst eine bloße Funktionsreferenz ohne zusätzlich Erklärungen würde viele Seiten füllen. Wir werden uns also auf einige konkrete Anwendungsbeispiele beschränken und daran Teilaspekte des Canvas-Elements beleuchten. Zunächst müssen wir aber ein solches Element in unser HTML5-Dokument setzen:

<canvas id="canvas" height="480" width="600">
    Dein Browser kann kein Canvas-Element. Dir entgeht unser 
    tolles Anwendungsbeispiel! 
</canvas>

Der Text zwischen dem öffnenden und dem schließenden <canvas>-Tag wird nur dann angezeigt, wenn der Browser das Element nicht darstellen kann. Die height- und width-Angaben verhalten sich wie bei einem <img>-Element – sie geben zwar die Größe des Elements vor, können aber von entsprechenden CSS-Regeln auch überstimmt werden.

Öffnet man diese Konstruktion jetzt in einem halbwegs modernen Browser (Firefox 3.5+, Opera 10+, Safari 4+, Chrome oder Internet Explorer 9) sieht man aber erst mal gar nichts. Das Canvas-Element ist in der Tat nichts weiter als eine leere Leinwand, die wir mit JavaScript befüllen müssen – ohne Programmierung läuft nichts. Also programmieren wir doch einmal etwas:

window.onload = function(){ 
    var canvas = document.getElementById('canvas'); 
    if(canvas.getContext){ 
        var context = canvas1.getContext('2d'); 
        context.fillStyle = "rgb(255, 0, 255)"; 
        context.fillRect(20, 20, 300, 240); 
    } 
}

Das Ergebnis sieht dann schon mal zumindest nicht nach Nichts aus:

hallowelt

Zur Erläuterung: Zeile 1 des Codeschnipsels (window.onload...) sorgt nur dafür, dass der folgende Code erst ausgeführt wird, wenn die Seite fertig geladen ist. Dann wird mit document.getElementById() eine Referenz auf das Canvas-Element in der Seite geholt und in Zeile 3 wird geprüft, ob das Element die Methode getContext() kennt. So kann zwischen Browsern mit Canvas-Unterstützung und jenen ohne unterschieden werden.

Die getContext()-Methode dient dazu, Zugriff auf die Zeichen-APIs (Kontexte genannt) des Canvas-Elements zu bekommen. In Zeile 4 wird auf diese Weise der Kontext mit der ID 2D in der Variablecontext gespeichert. Der 2D-Kontext ist der Standard-Zeichenkontext von HTML5, ein 3D-Kontext ist aber auch in Entwicklung. Der 2D-Kontext bietet uns mehrere Zeichenfunktionen und -Attribute, mit denen wir unsere Canvas zum Leben erwecken können.

In Zeile 5 wird ein solches Attribut, fillStyle (legt die Füllfarbe für alle folgenden Zeichenoperationen fest), auf Magenta festgelegt und Zeile 6 zeichnet mit dieser Farbe im Hinterkopf ein 300×240 Pixel großes Rechteck, dessen linke obere Ecke um je 20 Pixel auf X- und Y-Achse von der linken oberen Ecke des Canvas-Elements verschoben ist. Fertig ist unser „Hallo Welt“ für Canvas!

Der schwarze Rahmen wurde übrigens mit herkömmlichem CSS eingefügt. Da das Canvas-Element ein HTML-Element wie jedes andere ist, kann man es uneingeschränkt mit CSS und JavaScript traktieren – jQuery-Animationen und CSS3-Transformationen inklusive.

Auch wenn dieses Beispiel eher wenig praktische Relevanz hat, zeigt es einige der wichtigsten Prinzipien: Erstens laufen alle Zeichenoperationen über die API des Kontexts, nicht des Elements selbst, und zweitens müssen die Variablen für Zeichenoperationen erst gesetzt werden, bevor gezeichnet wird. Wir entscheiden uns erst für Magenta als Farbe und zeichnen danach.

Zeit für ein paar Beispiele mit (ein wenig) mehr Praxisbezug. Ein besonders praktisches Feature des Canvas-Elements ist, dass man es nicht zwingend händisch mit bunten Rechtecken füllen muss, sondern (unter anderem) per <img> eingebundene Bilder als Datenquelle nutzen kann. Und hat man diese Bilder einmal auf die Canvas gebracht, kann man sie Pixel für Pixel manipulieren.

Anwendungbeispiel 1: Fotomanipulation

Mit der Kontext-Methode drawImage() kann man ein im Dokument vorhandenes Bild auf die Canvas bannen. Man nehme dazu ein Bild sowie Canvas-Element …

<img id="original" height="320" width="406" src="original.jpg" alt="">
<canvas id="canvas" height="320" width="406"></canvas>

… mache gebrauch von der drawImage()-Methode …

window.onload = function(){ 
    var original = document.getElementById('original'); 
    var canvas   = document.getElementById('canvas'); 
    if(canvas.getContext){ 
        var context = canvas.getContext('2d'); 
        // Original-Bild auf die Canvas zeichnen
        context.drawImage(original, 0, 0); 
    } 
}

… schon kann man das Bild 1:1 kopieren (Original-<img> links, Canvas-Element rechts):

bildkopie

So weit, so unspektakulär. Hat man aber erst mal das Bild auf ein Canvas-Element gebracht, kann man mit der MethodegetImageData() Zugriff auf die Farbwerte jedes einzelnen Pixels bekommen und kann diese dann nach Herzenslust manipulieren. Als Argumente für getImageData() muss man die Eckpunkte des Canvas-Ausschnitts angeben, für den man die Pixel abgreifen möchte (Koordinaten der linken oberen Ecke + Maße des Ausschnitts).

Zurück erhält man ein Objekt, dessen Eigenschaft data ein Array mit den Farbwerten aller Pixel im Ausschnitt enthält –erst kommt der Rot-Wert des ersten Pixels, dann der Grün-Wert des ersten Pixels, der Blau-Wert des ersten Pixels, der Alpha-Wert des ersten Pixels, dann der Rot-Wert des zweiten Pixels und so weiter.

Wenn man in diesem Array nun alle RGB-Werte modifiziert (z.B. die Farben einfach umdreht) und das Endergebnis mit putImageData() wieder zurück auf die Canvas schreibt, macht das Ergebnis schon etwas mehr her:

window.onload = function(){ 
    var original = document.getElementById('original'); 
    var canvas   = document.getElementById('canvas'); 
    if(canvas.getContext){ 
        var context = canvas.getContext('2d'); 
        // Original-Bild auf die Canvas zeichnen
        context.drawImage(original, 0, 0); 
        // Pixeldaten auslesen, in imgData speichern 
        var imgData = context.getImageData(0, 0, canvas.width, canvas.height); 
        var r, g, b, i = 0; 
        // Alle Pixel durchlaufen ... 
        while(i < imgData.data.length){ 
            // ... Rot-Wert umkehren ... 
            imgData.data[i] = 255 – imgData.data[i++]; 
            // ... Grün-Wert umkehren ...
            imgData.data[i] = 255 – imgData.data[i++]; 
            // ... Blau-Wert umkehren ... 
            imgData.data[i] = 255 – imgData.data[i++]; 
            // ... und den Alpha-Wert unverändert lassen 
            i++; 
        }
        // Verändertes imgData auf die Canvas zeichnen 
        context.putImageData(imgData, 0, 0);
    } 
}
bildmanipulation

An dieser Stelle ist erwähnenswert, dass aufgrund der Same-Origin-Policy Bilddaten nicht auf lokalen Daten (z.B. c:\foo.html) hin- und herkopiert werden können; der Aufruf über einen richtigen Host (z.B. localhost) ist Pflicht.

Anwendungbeispiel 2: Malprogramm

Das Canvas-Element ist ein HTML-Element wie jedes andere, man kann es via CSS stylen, anklicken, und JavaScript-Funktionen in die normalen DOM-Events einhängen. Also ist eine dynamische Malfläche, Paint im Browser, kein Hexenwerk. Man nehme ein Canvas-Element und ein paar <div>-Elemente nebst CSS-Styles …

<style>
    div, canvas { cursor:crosshair; border:1px solid black; float:left; margin:1em; }
    div { cursor:pointer; height:40px; width:80px; }
</style>

<canvas id="canvas" height="320" width="406"></canvas>
<div id="red" style="background:red;"></div>
<div id="blue" style="background:blue;"></div>
<div id="green" style="background:green;"></div>

… und schon steht die Oberfläche für unser Canvas-Malprogramm:

malen1

Die bunten Boxen dienen zur Auswahl der Farbe, mit der auf dem Canvas-Element gemalt werden soll. Sie zum Funktionieren zu bringen ist einfach: beim Klick auf eine Farbbox muss sich nur context.fillStyle (wir erinnern uns, das ist der Wert, der die Füllfarbe für alle folgenden Zeichenoperationen festlegt) passend ändern.

var context = document.getElementById('canvas').getContext('2d');

// Rote Farbe wählen
document.getElementById('red').onclick = function(){
    context.fillStyle('red');
}
// Blaue Farbe wählen
document.getElementById('blue').onclick = function(){
    context.fillStyle('blue');
}
// Grüne Farbe wählen
document.getElementById('green').onclick = function(){
    context.fillStyle('green');
}

Schon fehlt nur noch die Programmierung des Zeichnens selbst. Hierfür nutzen wir das Mousemove-Event auf dem Canvas-Element und schalten bei den Mousedown- und Mouseup-Events zwischen „aktivem“ und „inaktivem“ Modus hin- und her, so dass nur gezeichnet wird, wenn auch tatsächlich die Maustaste gedrückt ist.

Ein hilfreiches Detail, das das folgende Codeschnipsel nutzt, ist dass die Kontext-Variable in ihrer Eigenschaft canvas immer eine Referenz auf das Canvas-Element, zu dem der Kontext gehört, bereithält. So ist die Programmierung der Events ein Kinderspiel:

// Aktiv-Modus hin- und herschalten
var active = false;
context.canvas.onmousedown = function(){
    active = true;
}
context.canvas.onmouseup = function(){
    active = false;
}

// Nur zeichnen wenn "active" true ist, d.h. die Maustaste gedrückt ist
context.canvas.onmousemove = function(event){
    var x = event.clientX - context.canvas.offsetLeft; // Mausposition X
    var y = event.clientY - context.canvas.offsetTop; // Mausposition Y
    if(active){
        context.fillRect(x - 5, y - 5, 10, 10);
    }
}

Und fertig! Schon kann man drauflosmalen:

malen2

Abspeichern kann man sein fertiges Meisterwerk im übrigen ganz einfach per Rechtsklick → Datei speichern unter.

Anwendungbeispiel 3: Pimp your Canvas API

Die beiden bisher gezeigten Beispiele sind etwas krude ausgefallen. Der Grund dafür liegt in der Canvas-API selbst, die sich wie eingangs erwähnt auf die allernötigsten Funktionen beschränkt. Sie kennt von Haus aus keine Objekte oder Animationsfunktionen, hat keine Methode für Kreise an Bord und macht einem ganz allgemein das Leben schwer. So muss man selbst für simpelste Grafiken wie dieses Strichmännchen …

strich

… in Canvas-Code mit den diversen Pfad-Funktionen viel zu viel tippen:

// Körper
context.beginPath();
context.moveTo(240, 100);
context.lineTo(240, 200);
// Beine
context.lineTo(190, 250);
context.moveTo(240, 200);
context.lineTo(290, 250);
// Arme
context.moveTo(240, 150);
context.lineTo(190, 150);
context.moveTo(240, 150);
context.lineTo(290, 150);
// Linien ziehen
context.lineWidth = 4;
context.strokeStyle = '#CC0000';
context.stroke();
context.closePath();
// Kopf
context.beginPath();
context.arc(240, 80, 35, 0, Math.PI * 2, false);
// Linien ziehen und füllen
context.fillStyle = '#CC0000';
context.fill();
context.stroke();
context.closePath();

Was tun? Frameworks oder andere Hilfsmittel bauen! Die Canvas-API ist gar nicht dafür gedacht, dass man direkt mit ihr Grafiken programmiert, sondern dass JavaScript-Bibliotheken auf sie aufsetzen oder dass IDEs den Canvas-Code generieren. Ein Framework zu programmieren ist kein Hexenwerk; mit nur 60 Zeilen Framework-Code lässt sich das Strichmänchen dank verketteter Befehle gleich viel einfacher zusammenbauen:

// Körper
context.moveTo(240, 100).lineTo(240, 200);
// Beine
context.lineTo(190, 250).moveTo(240, 200).lineTo(290, 250);
// Arme
context.moveTo(240, 150).lineTo(190, 150).moveTo(240, 150).lineTo(290, 150);
// Linien ziehen
context.set({'lineWidth': 4, 'strokeStyle': '#CC0000'}).stroke().closePath();
// Kopf
context.beginPath().arc(240, 80, 35, context.deg2rad(0), context.deg2rad(360), false);
// Linien ziehen
context.set('fillStyle', context.get('strokeStyle')).fill().stroke().closePath();

Canvas-Frameworks gibt es bereits wie Sand am Meer. Einige sind, wie das gezeigte Verkettungs-Framework, eher allgemeiner Natur, andere auf spezielle Aufgabengebiete zugeschnitten. Eine kleine Auswahl:

  • CamanJS bietet Funktionen für Bildmanipulation (Sepia-Effekt usw.)
  • EaselJS portiert das aus Flash bekannte Objekt- und Interaktionssystem nach Canvas
  • Für schicke Diagramme sorgt RGraph
  • Für den 3D-Kontext gibt es auch bereits mehrere Frameworks bzw. 3D-Engines, unter anderem CopperLicht, GLGE und SceneJS

Fazit

Das Canvas-Element haucht unseren Browsern ganz neue Möglichkeiten ein – wo man buchstäblich jede nur denkbare Grafik rendern kann, sind der Kreativität keine Grenzen gesetzt. Zwar sind einige fiese Klippen, die zum Beispiel das Themen Barrierefreiheit und Performance betreffen, noch nicht endgültig umschifft, doch alles in allem ist das Canvas-Element ein Werkzeug, mit dem wir in naher Zukunft sicher viel Vergnügen haben haben. Es funktioniert in allen modernen Browsern zuverlässig (mit diesem Trick sogar in den Internet Explorern 6 bis 8) und bietet viel Potenzial – was will man mehr?

Über den Autor

Peter Kröner ist selbstständiger Webdesigner und -entwickler, Autor des HTML5-Buchs und Dozent für alle Fragen rund um HTML5 aus der Nähe von Osnabrück. Auf peterkroener.de bloggt er über alle Themen rund um Web(zukunfts)technologie.

Webentwickler aufgepasst!

Dev_unplugged

Microsoft hat einen Wettbewerb für HTML5-Entwickler gestartet. Bei Interesse schaut einfach bei www.beautyoftheweb.com/#/unplugged vorbei.