Copyright © 2011 O'Reilly Verlag GmbH & Co. KG
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.
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 |
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:
Der Code, der auf Clientseite zur Erstellung der Grafiken genutzt wird, ist normalerweise erheblich kleiner als die Bilder selbst und spart damit eine Menge Bandbreite.
Dass Aufgaben vom Server auf den Client verlagert werden, reduziert die Last auf dem Server und kann die Ausgaben für Hardware um einiges mindern.
Die clientseitige Grafikgenerierung fügt sich gut in die Architektur von Ajax-Anwendungen ein, in denen der Server die Daten stellt und sich der Client um die Darstellung dieser Daten kümmert.
Der Client kann Grafiken schnell und dynamisch neu zeichnen. Das ermöglicht grafikintensive Anwendungen (wie Spiele und Simulationen), die schlicht nicht machbar sind, wenn jeder Frame einzeln von einem Server heruntergeladen werden muss.
Das Schreiben von Grafikprogrammen ist ein Vergnügen, und das
<canvas>
-Tag lindert für den
Webentwickler die Pein, die die Arbeit mit dem DOM mit sich bringen
kann.
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>
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:
Wie man Pfade definiert und die Linie des Pfads zeichnet oder das Innere eines Pfads füllt.
Wie man die Grafikattribute des Canvas-Kontext-Objekts setzt und abfragt und wie man den aktuellen Status dieser Attribute speichert und wiederherstellt.
Canvas-Maße, das Standard-Canvas-Koordinatensystem und wie man dieses Koordinatensystem transformiert.
Die verschiedenen Methoden zum Zeichnen von Kurven, die von der Canvas-API definiert werden.
Einige spezielle Hilfsmethoden zum Zeichnen von Rechtecken.
Wie man Farben angibt, mit Transparenz arbeitet, Farbverläufe zeichnet und Bildmuster wiederholt.
Die Attribute, die die Strichbreite und das Erscheinungsbild der Linienendpunkte und -eckpunkte steuert.
Wie man Text in ein <canvas>
schreibt.
Wie man Grafiken so beschneidet, dass nichts außerhalb der von Ihnen angegebenen Region gezeichnet wird.
Wie man Grafiken Schlagschatten hinzufügt.
Wie man Bilder in ein Canvas zeichnet (und optional skaliert) und wie man den Inhalt eines Canvas herauszieht und als Bild speichert.
Wie man den Compositing-Prozess steuert, über den neu gezeichnete (durchscheinende) Pixel mit den im Canvas vorhandenen Pixeln kombiniert werden.
Wie man die rohen Rot-, Grün-, Blau- und Alphawerte (Transparenz) von Pixeln im Canvas abfragt und setzt.
Wie man ermittelt, ob über etwas, das Sie auf das Canvas gezeichnet haben, ein Mausereignis eingetreten ist.
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.
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.
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
fill()