1.

Canvas

kurz & gut

David Flanagan

Lars Schulten

Die Informationen in diesem Buch wurden mit größter Sorgfalt erarbeitet. Dennoch können Fehler nicht vollständig ausgeschlossen werden. Verlag, Autoren und Übersetzer übernehmen keine juristische Verantwortung oder irgendeine Haftung für eventuell verbliebene Fehler und deren Folgen. Alle Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt und sind möglicherweise eingetragene Warenzeichen. Der Verlag richtet sich im Wesentlichen nach den Schreibweisen der Hersteller. Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

Satz: Reemers Publishing Services GmbH, Krefeld, www.reemers.de, Druck: fgb freiburger graphische betriebe; www.fgb.de

Dieses Buch ist auf 100% chlorfrei gebleichtem Papier gedruckt.

O'Reilly Verlag GmbH & Co. KGBalthasarstr. 81 50670Köln kommentar@oreilly.de

Vorwort

Dieses Buch dokumentiert die JavaScript-API zum Zeichnen von Grafiken in ein HTML-<canvas>-Tag. Es setzt voraus, dass Sie die Programmiersprache JavaScript kennen und zumindest grundlegend mit dem Einsatz von JavaScript in Webseiten vertraut sind. Kapitel 1 ist eine Einführung, die alle Canvas-Funktionen erklärt und anhand von Beispielen illustriert. Kapitel 2 beinhaltet eine Referenz zu den Canvas-bezogenen Klassen, Methoden und Eigenschaften.

Dieses Buch ist ein Auszug aus dem erheblich umfangreicheren Buch JavaScript: Das umfassende Handbuch; mein Verlag und ich waren der Meinung, dass das <canvas>-Tag eine so spannende HTML5-Komponente ist, dass es ein kompaktes eigenes Buch verdient. Da die Canvas-API recht überschaubar ist, kann sie in diesem kurzen Buch vollständig beschrieben werden.

Mein Dank gilt Raffaele Cecco, der das Buch und die Codebeispiele sorgfältig durchgesehen hat. Außerdem danke ich meinem Lektor, Mike Loukides, für seine Begeisterung für dieses Projekt, und Simon St. Laurent, der das Material vom Format für das »Umfassende Handbuch« in das »kurz & gut«-Format überführt hat.

Die Beispiele in diesem Buch können von der Webseite zum Buch heruntergeladen werden, auf der auch eventuelle Fehler aufgeführt werden, sollten solche nach Veröffentlichung des Buches entdeckt werden:

http://www.oreilly.de/catalog/canvasprger

Kapitel 1. Canvas-Tutorial

Dieses Buch erläutert, wie man mit JavaScript und dem HTML-<canvas>- Tag Grafiken in Webseiten zeichnet. Die Möglichkeit, komplexe Grafiken dynamisch im Webbrowser zu generieren, statt sie von einem Server herunterzuladen, stellt eine Revolution dar:

Das <canvas>-Tag tritt nicht an sich in Erscheinung, sondern es erstellt eine Zeichenfläche im Dokument und bietet clientseitigem JavaScript eine mächtige API zum Zeichnen. Das <canvas>-Tag wird von HTML5 standardisiert, treibt sich aber schon deutlich länger herum. Es wurde von Apple in Safari 1.3 eingeführt und wird von Firefox seit Version 1.5 und Opera seit Version 9 unterstützt. Außerdem wird es von allen Versionen von Chrome unterstützt. Vom Internet Explorer wird es erst ab Version 9 unterstützt, kann in IE 6, 7 und 8 aber hinreichend gut emuliert werden.

Große Teile der Canvas-Zeichen-API werden nicht im <canvas>-Element selbst definiert, sondern auf einem » Zeichenkontext-Objekt«, das Sie sich mit der getContext()-Methode des Canvas beschaffen. Rufen Sie getContext() mit dem Argument 2d auf, erhalten Sie ein CanvasRenderingContext2D-Objekt, über das Sie zweidimensionale Grafiken in das Canvas zeichnen können. Es ist wichtig, sich darüber bewusst zu sein, dass das Canvas-Element und sein Kontext-Objekt zwei sehr verschiedene Objekte sind. Da der Klassenname so lang ist, verweise ich auf das CanvasRenderingContext2D-Objekt nur selten mit diesem Namen, sondern nenne es einfach das » Kontext-Objekt«. Und wenn ich von der »Canvas-API« spreche, meine ich damit in der Regel »die Methoden des CanvasRenderingContext2D-Objekts«. Da der lange Klassenname CanvasRenderingContext2D nicht gut auf diese schmalen Seiten passt, wird er außerdem im auf diese Einführung folgenden Referenzabschnitt mit CRC abgekürzt.

Ein einfaches Beispiel für die Canvas-API sehen Sie im folgenden Code, der ein rotes Rechteck und einen blauen Kreis in <canvas>-Tags schreibt und eine Ausgabe wie die generiert, die Sie in Abbildung 1.1 sehen.

<body>
Das ist ein rotes Rechteck:
<canvas id="square" width=10 height=10></canvas>.
Das ist ein blauer Kreis:
<canvas id="circle" width=10 height=10></canvas>.
<script>
// Das erste Canvas-Element und seinen Kontext abrufen 
var canvas = document.getElementById("square");  
var context = canvas.getContext("2d");
// Etwas in das Canvas zeichnen
context.fillStyle = "#f00";  // Die Farbe auf Rot setzen
context.fillRect(0,0,10,10); // Ein kleines Rechteck 
                             // füllen

// Das zweite Canvas und seinen Kontext abrufen
canvas = document.getElementById("circle");
context = canvas.getContext("2d");
// Einen Pfad beginnen und ihm einen Kreis hinzufügen 
context.beginPath(); 
context.arc(5, 5, 5, 0, 2*Math.PI, true);
context.fillStyle = "#00f";  // Die Füllfarbe auf Blau 
                             // setzen
context.fill();              // Den Pfad füllen
</script>
</body>
Einfache Canvas-Grafiken

Abbildung 1.1 Einfache Canvas-Grafiken

Die Canvas-API beschreibt komplexe Figuren als » Pfade« von Linien und Kurven, die gezeichnet oder gefüllt werden können. Ein Pfad wird durch eine Folge von Methodenaufrufen definiert, wie beispielsweise die beginPath()- und arc()-Aufrufe aus dem vorangegangenen Code. Nachdem ein Pfad definiert ist, operieren andere Methoden wie fill() auf diesem Pfad. Verschiedene Eigenschaften des Kontext-Objekts wie fillStyle legen fest, wie diese Operationen ausgeführt werden. Die nächsten Unterabschnitte erläutern die folgenden Dinge:

Dieses Kapitel endet mit einem praktischen Beispiel, das das <canvas>-Tag nutzt, um kleine Inline-Diagramme zu zeichnen, die als Sparklines bezeichnet werden. Auf dieses Einführungskapitel folgt eine Referenz, die die Canvas-API in allen Details dokumentiert.

Viele der folgenden <canvas>-Codebeispiele operieren auf einer Variablen mit dem Namen c. Diese Variable hält das CanvasRenderingContext2D-Objekt des Canvas fest. Der Code, der diese Variable initialisiert, wird üblicherweise jedoch nicht gezeigt. Diese Beispiele funktionieren nur, wenn Sie das HTML-Markup mit einem Canvas mit den entsprechenden width- und height-Attributen haben und dann Code wie folgenden ergänzen, um die Variable c zu initialisieren:

var canvas = document.getElementById("my_canvas_id");
var c = canvas.getContext('2d');

Alle nachfolgenden Figuren werden mit JavaScript-Code generiert, der in ein <canvas>-Tag zeichnet, üblicherweise in ein großes Canvas, das nicht auf dem Bildschirm angezeigt wird, um druckfähige Grafiken mit hoher Auflösung zu erzeugen.

Linien zeichnen und Polygone füllen

Wenn Sie Linien in ein Canvas zeichnen und die von ihnen eingeschlossenen Flächen füllen wollen, definieren Sie zunächst einen Pfad. Ein Pfad ist eine Folge von einem oder mehreren Teilpfaden. Ein Teilpfad ist eine Folge von zwei oder mehr Punkten, die durch Liniensegmente (oder, wie wir später sehen werden, Kurvensegmente) verbunden sind. Einen neuen Pfad beginnen Sie mit der Methode beginPath(). Einen neuen Teilpfad beginnen Sie mit der Methode moveTo(). Wenn Sie den Startpunkt eines Teilpfads mit moveTo() eingerichtet haben, können Sie diesen Punkt über eine Gerade mit einem neuen Punkt verbinden, indem Sie die Methode lineTo() aufrufen. Der folgende Code definiert einen Pfad mit zwei Liniensegmenten:

c.beginPath();      // Einen neuen Pfad beginnen
c.moveTo(20, 20);   // Einen Teilpfad bei (20,20) beginnen
c.lineTo(120, 120); // Eine Linie nach (120,120) ziehen
c.lineTo(20, 120);  // Eine weitere nach (20,120)ziehen

Der vorangehende Code definiert nur einen Pfad. Er zeichnet noch nichts auf das Canvas. Rufen Sie die Methode stroke() auf, um die beiden Liniensegmente im Pfad zu zeichnen (oder »zu ziehen«). Rufen Sie die Methode fill() auf, um die von diesen Liniensegmenten definierte Fläche zu füllen:

c.fill();      // Einen dreieckigen Bereich füllen
c.stroke();    // Die beiden Seiten des Dreiecks zeichnen

Der vorangehende Code (sowie etwas zusätzlicher Code, der die Strichbreite und die Füllfarbe setzt) erzeugt die in Abbildung 1.2 gezeigte Zeichnung.

Ein einfacher Pfad, gefüllt und gezogen

Abbildung 1.2 Ein einfacher Pfad, gefüllt und gezogen

Beachten Sie, dass der oben definierte Teilpfad » offen« ist. Er besteht aus zwei Liniensegmenten, deren Endpunkt nicht wieder mit dem Startpunkt verbunden ist. Das bedeutet, dass der Pfad keine Fläche einschließt. Die Methode fill() füllt offene Teilpfade, indem sie so tut, als wäre der Endpunkt über eine gerade Linie mit dem Startpunkt verbunden. Deswegen füllt der vorangehende Code ein Dreieck, zeichnet aber nur zwei Seiten dieses Dreiecks.

Wenn alle drei Seiten des zuvor gezeigten Dreiecks gezeichnet werden sollen, müssen Sie die Methode closePath() aufrufen, um den Endpunkt des Teilpfads mit dem Startpunkt zu verbinden. (Sie könnten auch lineTo(20,20) aufrufen, hätten damit aber drei Liniensegmente, die dann einen Start- und Endpunkt teilen, aber nicht wirklich geschlossen sind. Zeichnen Sie breite Linien, ist das sichtbare Ergebnis besser, wenn Sie closePath() nutzen.)

Es gibt zwei weitere wichtige Punkte, die Sie sich in Bezug auf stroke() und fill() merken sollten. Zunächst operieren beide Methoden auf allen Teilpfaden des aktuellen Pfads. Angenommen, wir hätten einen weiteren Teilpfad im Code:

c.moveTo(300,100); // Einen neuen Teilpfad bei (300,100) 
                   // beginnen
c.lineTo(300,200); // Eine vertikale Linie nach (300,200) 
                   // ziehen

Würden wir jetzt stroke() aufrufen, würden wir zwei verbundene Schenkel eines Dreiecks zeichnen und eine nicht verbundene vertikale Linie.

Der zweite Punkt ist, dass weder stroke() noch fill() den aktuellen Pfad ändern: Sie können fill() aufrufen, und der Pfad ist immer noch da, wenn Sie stroke() aufrufen. Wenn Sie die Arbeit mit dem Pfad abgeschlossen haben und einen anderen Pfad eröffnen wollen, dürfen Sie nicht vergessen, beginPath()

definiert eine Funktion zum Zeichnen gleichseitiger Polygone und illustriert die Verwendung von , und zur Definition von Teilpfaden sowie und zum Zeichnen dieser Pfade. Es erzeugt die in gezeigte Zeichnung.

// Definiert ein gleichseitiges Polygon mit n Seiten, beim 
// Mittelpunkt (x,y) mit dem Radius r. Die Ecken werden 
// gleichmäßig auf dem Rand eines Kreises verteilt. Die erste 
// Ecke wird über dem Mittelpunkt oder beim angegebenen 
// Winkel gezeichnet. Die Linien werden im Uhrzeigersinn  
// gezogen, es sei denn, das letzte Argument ist true.
function polygon(c,n,x,y,r,angle,counterclockwise) {
    angle = angle || 0;
    counterclockwise = counterclockwise || false;
    // Die Position der Ecke berechnen und dort einen Teilpfad
    // beginnen
    c.moveTo(x + r*Math.sin(angle), 
             y - r*Math.cos(angle));
    var delta = 2*Math.PI/n;     // Der Winkel zwischen den
                                 // Seiten
    for(var i = 1; i < n; i++) { // Für die verbleibenden
                                 // Ecken
        // Winkel dieser Seite berechnen
        angle += counterclockwise?-delta:delta; 
        // Die Position einer Ecke berechnen und eine Linie 
        // dorthin ziehen
        c.lineTo(x + r*Math.sin(angle),
                 y - r*Math.cos(angle));
    }
    c.closePath(); // Die letzte Ecke mit der ersten verbinden
}

// Einen neuen Pfad beginnen und ihm Polygon-Teilpfade 
// hinzufügen
c.beginPath();
polygon(c, 3, 50, 70, 50);              // Dreieck
polygon(c, 4, 150, 60, 50, Math.PI/4);  // Quadrat
polygon(c, 5, 255, 55, 50);             // Fünfeck
polygon(c, 6, 365, 53, 50, Math.PI/6);  // Sechseck
// Ein kleines Rechteck gegen die Uhr in das Sechseck zeichnen
polygon(c, 4, 365, 53, 20, Math.PI/4, true); 

// Eigenschaften setzen, die steuern, wie die Grafik aussieht
c.fillStyle = "#ccc";    // Hellgraues Inneres,
c.strokeStyle = "#008";  // umrahmt von dunkelblauen Linien
c.lineWidth = 5;         // mit fünf Pixeln Breite.

// Jetzt alle Polygone zeichnen (jedes im eigenen Teilpfad)
c.fill();                // Die Figuren füllen
c.stroke();              // Die Ränder zeichnen
Gleichseitige Polygone

fill()