decocode decocode deco    

Zeichnen auf Canvas #

Diese Seite wird zurzeit überarbeitet, weshalb Teile der hier gemachten Informationen unvollständig und/oder fehlerhaft sein können!

Übersicht
Linien
Polygone
Rechtecke
Kreisbögen
Bézier-Kurven
Farbverläufe
Muster
Schatten
Transformationen
Grafikdateien
Text

Mithilfe von JavaScript kann innerhalb des <canvas>-Elements von HTML5 eine Rastergrafik im PNG-Format erstellt werden, die sich bei Bedarf auch animieren lässt. Um einen Einstieg in das Konzept zu vermitteln, werden hier die einfachsten grafischen Formen beschrieben. Viele Formen finden sich bei dem Vektorgrafik-Format SVG wieder, weshalb diese Einführung hier eng an das SVG-Tutorial angelehnt ist.

Bei der Verwendung dieser Technik ist darauf zu achten, dass ältere Browser, insbesondere der Internet Explorer bis Version 8, das <canvas>-Element nicht unterstützen. Außerdem unterstützt dieser Browser die Methode setLineDash() erst ab Version 11.

Hier zunächst ein Beispiel einer HTML-Datei mit einem <canvas>-Element und dem dazugehörigen JavaScript:

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html>
<html>
  <head>
    <title>Canvas</title>
    <meta charset='UTF-8'>
    <meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
    <style type='text/css'>
      canvas { border:1px solid silver; }
      canvas b { display:inline-block; margin:1px; padding:4px; width:140px; background-color:red; color:white; font-family:monospace; font-size:12px; }
      canvas b:before { content:'Der Inhalt kann nicht dargestellt werden, da der Browser das canvas-Element nicht unterstützt!'; }
    </style>
  </head>
  <body>
    <canvas id='test'><b></b></canvas>
    <script type='text/javascript'>
      // Dieses Skript erzeugt die Grafik im canvas-Element
      
      var cv = document.getElementById('test');
      cv.width = 350;
      cv.height = 180;
      var cx = cv.getContext('2d');
      
      cx.lineWidth = 5;
      cx.strokeStyle = 'red';
      
      cx.beginPath();
      cx.moveTo(10,10);
      cx.lineTo(190,90);
      cx.stroke();
    </script>
  </body>
</html>

Die HTML-Elemente innerhalb des <canvas>-Elements werden angezeigt, falls der verwendete Browser dieses Element nicht unterstützt, also beispielsweise wenn er zu alt ist, um HTML5-kompatibel zu sein.

Die Zeilen 18 bis 21 können als Prolog des Skripts angesehen werden, in dem über die Variable cv (canvas) auf das HTML-Element mit der gewünschten id (hier: test) Bezug genommen wird. Breite und Höhe der Grafik werden festgelegt (die von der Größe des HTML-Elements abweichen können) und der 2D-Kontext des Elements wird in der Variable cx (context) festgehalten. Dabei gelten folgende Regeln:

  • Maße werden in Pixeln angegeben.
  • Das Koordinatensystem hat seinen Ursprung (Nullpunkt) oben/links.
  • Alle Koordinaten werden immer als absolute Werte relativ zum Nullpunkt des Koordinatensystems angegeben.
  • Objekte dürfen die Bilddimensionen überragen.
  • Koordinaten geben das Zentrum einer Linie an.
  • Farben können wie in CSS mit Farbnamen, als RGB- oder HSL(A)-Wert angegeben werden.
  • Der Hintergrund der Grafik ist standardmäßig transparent.
  • Objekte überlagern sich standardmäßig in der Reihenfolge ihres Auftretens.

Innerhalb des JavaScript-Codes, der die gewünschte Grafik erzeugt, werden nun sämtliche für die Grafik benötigten Angaben notiert. Eigenschaften wie Linienstärke, -farbe usw. gelten solange auch für neu definierte Objekte, bis diese Eigenschaften geändert oder zurückgesetzt werden. Sie müssen also nicht für jedes Objekt einzeln notiert werden.

Neue Pfade werden mit beginPath() begonnen. Der Anfangspunkt eines Pfades wird mit moveTo(x,y) festgelegt. Anschließend werden sämtliche Details des Pfades notiert und der so beschriebene Pfad schließlich mit stroke() ausgeführt.

Linien #

Linien werden mit lineTo(x,y) definiert. Hiermit wird eine Linie vom zuletzt festgelegten Punkt des Pfades zu den Koordinaten x und y gezeichnet.

Um die Darstellung der Linie zu bestimmen, stehen folgende Eigenschaften zur Verfügung:

strokeStyle für die Linienfarbe (default: #000000)
lineWidth für die Linienstärke (default: 1)
globalAlpha für den Alphakanal bzw. die Opazität (Deckkraft) der Linie (default: 1)
lineCap für die Darstellung des Abschlusses einer Linie (butt, round, square)
setLineDash() für Unterbrechungen
lineDashOffset steuert den Beginn der Unterbrechungen

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
      cx.beginPath();
      cx.moveTo(10,10);
      cx.lineTo(340,10);  // line0
      cx.stroke();

      cx.strokeStyle = 'cyan';

      cx.beginPath();
      cx.moveTo(10,20);
      cx.lineTo(340,20);  // line1
      cx.stroke();

      cx.beginPath();
      cx.moveTo(10,30.5);
      cx.lineTo(340,30.5);  // line2
      cx.stroke();

      cx.lineWidth = 2;

      cx.beginPath();
      cx.moveTo(10,40);
      cx.lineTo(340,40);  // line3
      cx.stroke();

      cx.lineWidth = 10;
      cx.setLineDash([20,5]);
      cx.lineDashOffset = 8;

      cx.beginPath();
      cx.moveTo(10,55);
      cx.lineTo(340,55);  // line4
      cx.stroke();

      cx.strokeStyle = 'green';
      cx.setLineDash([]);

      cx.beginPath();
      cx.moveTo(330,10);
      cx.lineTo(10,120);  // line5
      cx.stroke();

      cx.strokeStyle = 'cyan';
      cx.lineCap = 'square';
      cx.globalAlpha = .5;

      cx.beginPath();
      cx.moveTo(10,75);
      cx.lineTo(340,75);  // line6
      cx.stroke();

      cx.lineCap = 'round';
      cx.globalAlpha = 1;

      cx.beginPath();
      cx.moveTo(10,95);
      cx.lineTo(340,95);  // line7
      cx.stroke();

Unten sieht man die von dem Skript erzeugte Grafik. Die oberste Linie (line0) wird mit schwarzer Farbe dargestellt, da keine Farbe angegeben wurde. Die obersten beiden Linien (line0 und line1) haben im Skript die Breite 1px (da keine Breite angegeben wurde), während sie in der Grafik über zwei Pixel ausgedehnt werden. Die Ursache hierfür liegt in der Position, da die Angabe moveTo(10,10) das Zentrum der Linie genau auf die Grenze zwischen zwei Pixelzeilen verortet. Als Kompromiss werden daraufhin die Pixelzeilen ober- und unterhalb dieses Wertes mit der halben Opazität gefüllt. Möchte man dieses Verhalten verhindern, so muss man entweder einen ganzzahlig durch 2 teilbaren Wert für die Linienstärke angeben oder die Linie auf die Mitte einer Pixelzeile verorten, wie im Beispiel bei line2 mit den Werten moveTo(10,30.5). Dieses Verhalten ist allerdings nur bei einer Skalierung von 100 % relevant, da die Darstellung bei einer anderen Vergrößerung oder Verkleinerung natürlich entsprechend anders erfolgt.
Die Linie line3 besitzt nun wirklich 2 Pixel Stärke, und Linie line4 ist 10 Pixel stark. Diese Linie wurde zusätzlich durch die Eigenschaft setLineDash() unterbrochen. Über die Eigenschaft lineDashOffset kann der Beginn des Intervalls der Unterbrechung beeinflusst werden.
Für die nächste Linie line6 gelten zusätzlich die Eigenschaften lineCap = 'square' und globalAlpha = .5. Das bedeutet, dass die Linie an beiden Enden mit einem square (Rechteck) abgeschlossen (und dadurch etwas länger) wird und zu 50 % opak ist. Linie line7 wird durch die Eigenschaft lineCap = 'round' an den Enden abgerundet.
Die grüne diagonale Linie line5 wird nur von den letzten beiden Linien überdeckt, da sie im Skript vor diesen definiert wurde.

Polygone (Linienzüge) #

Polygone sind offene Pfade, die aus einer beliebigen Kette von Linien bestehen. Ihre Enden gelten zunächst als unverbunden, selbst wenn sie sich am gleichen Punkt befinden.

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
      cx.strokeStyle = 'red';
      cx.lineWidth = 6;
      cx.miterLimit = 1.9;
      cx.fillStyle = 'green';
      
      cx.beginPath();
      cx.moveTo(15,35);
      cx.lineTo(20,20);  // line1
      cx.lineTo(40,10);
      cx.lineTo(60,60);
      cx.lineTo(80,10);
      cx.lineTo(100,70);
      cx.lineTo(102,20);
      cx.lineTo(130,60);
      cx.lineTo(150,20);
      cx.lineTo(170,40);
      cx.fill();
      cx.stroke();

      cx.strokeStyle = 'blue';
      cx.lineWidth = 3;
      cx.miterLimit = 10;
      cx.fillStyle = 'cyan';
      
      cx.beginPath();
      cx.moveTo(60,75);
      cx.lineTo(90,150);  // line2
      cx.lineTo(18,104);
      cx.lineTo(102,104);
      cx.lineTo(30,150);
      cx.fill();
      cx.stroke();

      cx.strokeStyle = 'yellow';
      cx.setLineDash([10,5]);
      
      cx.beginPath();
      cx.moveTo(230,30);
      cx.lineTo(280,30);  // line3
      cx.lineTo(280,70);
      cx.lineTo(200,70);
      cx.lineTo(200,100);
      cx.lineTo(220,100);
      cx.lineTo(220,140);
      cx.lineTo(250,140);
      cx.stroke();

Polygone können mit einer Füllfarbe gefüllt werden, wenn diese Farbe mit fillStyle angegeben wird. Die Füllung wird mit fill() ausgeführt. Wird fill() nach stroke() ausgeführt, befindet sich die Füllung oberhalb der Linie des Pfades ausgehend von der Mitte dieser Linie. Das bedeutet, dass die Hälfte der Linie von der Füllung überdeckt wird (hier nicht dargestellt).

Polygone automatisch schließen #

Um ein Polygon zu schließen und damit einen geschlossenen Pfad zu erzeugen wird closePath() verwendet. Auf diese Weise werden Anfangs- und Endpunkt des Pfades automatisch geschlossen.

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
      cx.strokeStyle = 'red';
      cx.lineWidth = 6;
      cx.miterLimit = 1.9;
      cx.fillStyle = 'green';
      
      cx.beginPath();
      cx.moveTo(15,35);
      cx.lineTo(20,20);  // line1
      cx.lineTo(40,10);
      cx.lineTo(60,60);
      cx.lineTo(80,10);
      cx.lineTo(100,70);
      cx.lineTo(102,20);
      cx.lineTo(130,60);
      cx.lineTo(150,20);
      cx.lineTo(170,40);
      cx.closePath();
      cx.fill();
      cx.stroke();

      cx.strokeStyle = 'blue';
      cx.lineWidth = 3;
      cx.miterLimit = 10;
      cx.fillStyle = 'cyan';
      
      cx.beginPath();
      cx.moveTo(60,75);
      cx.lineTo(90,150);  // line2
      cx.lineTo(18,104);
      cx.lineTo(102,104);
      cx.lineTo(30,150);
      cx.closePath();
      cx.fill();
      cx.stroke();

      cx.strokeStyle = 'yellow';
      cx.setLineDash([10,5]);
      
      cx.beginPath();
      cx.moveTo(230,30);
      cx.lineTo(280,30);  // line3
      cx.lineTo(280,70);
      cx.lineTo(200,70);
      cx.lineTo(200,100);
      cx.lineTo(220,100);
      cx.lineTo(220,140);
      cx.lineTo(250,140);
      cx.closePath();
      cx.stroke();

Dieses Beispiel unterscheidet sich von dem Beispiel mit offenen Pfaden lediglich durch die Verwendung von closePath(). Alle anderen Eigenschaften sind indentisch, um den Unterschied zu verdeutlichen.

Rechtecke #

Rechtecke werden mit strokeRect() für die Linie und mit fillRect() für die Füllung definiert. Die Methoden erwarten vier Argumente: die Koordinaten für die linke obere Ecke (x, y) und die Dimensionen des Rechtecks (width, height).

Hier ein paar Beispiele. Es sei darauf hingewiesen, dass im Gegensatz zu SVG die Linie und die Füllung von Rechtecken immer eigenständige unabhängige Objekte darstellen. Abgerundete Ecken müssen aus einer Kombination von Linien und Bögen erzeugt werden.

Mit clearRect() kann ein rechteckiger Bereich gelöscht werden. Diese Methode erwartet die gleichen vier Argumente wie strokeRect() bzw. fillRect().

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
      cx.globalAlpha = .8;
      cx.strokeStyle = 'red';
      cx.lineWidth = 6;
      cx.setLineDash([14,4]);
      cx.lineDashOffset = 12;
      cx.strokeRect(10,10,80,60);

      cx.globalAlpha = 1;
      cx.fillStyle = 'black';
      cx.fillRect(130,10,120,60);

      cx.fillStyle = 'yellow';
      cx.fillRect(50.5,40.5,120,90);
      cx.strokeStyle = 'blue';
      cx.lineWidth = 1;
      cx.setLineDash([]);
      cx.strokeRect(50.5,40.5,120,90);

      cx.globalAlpha = .8;
      cx.fillStyle = 'green';
      cx.fillRect(13,103,74,54);
      cx.strokeStyle = 'red';
      cx.lineWidth = 6;
      cx.strokeRect(10,100,80,60);

      cx.globalAlpha = 1;
      cx.fillStyle = 'cyan';
      cx.fillRect(130,100,80,60);
      cx.globalAlpha = .5;
      cx.lineWidth = 10;
      cx.strokeRect(130,100,80,60);

      cx.globalAlpha = 1;
      cx.fillStyle = 'blue';
      cx.lineWidth = 16;
      cx.lineJoin = 'bevel';
      cx.fillRect(270,20,50,120);
      cx.strokeRect(270,20,50,120);

      cx.clearRect(270,90,20,30);

Die Eigenschaften setLineDash() und lineDashOffset bewirken die Unterbrechungen der Linie des ersten Rechtecks.

Das Zentrum der Konturlinie verläuft genauso wie bei einer regulären Linie entlang der Außenkanten des Rechtecks.

Das blaue Rechteck mit rotem Rand auf der rechten Seite demonstriert die Wirkung der Eigenschaft lineJoin. Mögliche Werte sind miter (Gehrung), round (Rundung) und bevel (Fase), wobei miter der Vorgabewert ist.

Rechtecke können auch Teil eines Pfades sein. Die Methode rect() erwartet dazu vier Argumente:

• x- und y-Koordinate der oberen linken Ecke des Rechtecks
• Breite und Höhe des Rechtecks

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
12
      cx.strokeStyle = 'black';
      cx.fillStyle = 'red';
      cx.lineWidth = 5;
      
      cx.beginPath();
      cx.moveTo(50,20);
      cx.lineTo(200,30);
      cx.rect(30,40,80,60);
      cx.lineTo(300,100);
      cx.lineTo(50,130);
      cx.fill();
      cx.stroke();

Die obere linke Ecke des Rechecks wird nicht automatisch mit dem letzten Punkt des Pfades verbunden; dies kann aber bei Bedarf mit einem entsprechenden lineTo() nachgeholt werden.

Dagegen wird ein auf das Rechteck folgender Punkt des Pfades automatisch mit dieser Ecke verbunden; dies kann aber bei Bedarf mit einem entsprechenden moveTo() unterbunden werden.

Kreisbögen #

Kreisbögen werden mit arc() erzeugt, wobei die Methode mindestens fünf Argumente erwartet:

• x- und y-Koordinate des Mittelpunktes
• den Radius
• den Winkel des Startpunktes
• den Winkel des Endpunktes

Die Winkel werden nicht in Grad, sondern in Radiant (Bogenmaß) angegeben, wobei der Vollkreis von 360° im Bogenmaß 2π rad entspricht. Möchte man Grad in Radiant umrechnen, so multipliziert man den Gradwert daher mit (π / 180).

Der Winkel 0 rad (0°) befindet sich in positiver x-Richtung, sozusagen bei ›Ost‹ (Kompassrose) oder ›3 Uhr‹ (Zifferblatt einer Uhr). Positive Winkel werden im Uhrzeigersinn gemessen. 0,5π rad (90°) dementsprechend in positiver y-Richtung bzw. ›Süd‹ oder ›6 Uhr‹, 1π rad (180°) in negativer x-Richtung bzw. ›West‹ oder ›9 Uhr‹ und 1,5π rad (270°) in negativer y-Richtung bzw. ›Nord‹ oder ›12 Uhr‹.

Zwischen den beiden angegebenen Winkeln wird der Kreisbogen im Uhrzeigersinn gezeichnet. Soll der Kreisbogen gegen den Uhrzeigersinn gezeichnet werden, so gibt man als sechstes Argument den Wert 1 an.

Wurde der Startpunkt des Pfades zuvor mit moveTo() festgelegt, so wird von diesem Punkt zunächst eine Linie zum Startpunkt des Kreisbogens gezeichnet.

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
      var k = Math.PI / 180;

      cx.strokeStyle = 'yellow';
      cx.lineWidth = 2;
      cx.beginPath();
      cx.arc(80,70,50,270 * k,180 * k);
      cx.stroke();

      cx.strokeStyle = 'green';
      cx.beginPath();
      cx.moveTo(120,120);
      cx.arc(120,120,50,0,90 * k,1);
      cx.stroke();

      cx.fillStyle = 'red';
      cx.beginPath();
      cx.arc(250,70,20,0,2 * Math.PI);
      cx.fill();

Die farbliche Darstellung folgt prinzipiell den gleichen Regeln wie bei Linien.

Bézier-Kurven #

Bézier-Kurven werden durch die Methoden quadraticCurveTo() für quadratische Bézier-Kurven mit einem Kontrollpunkt bzw. bezierCurveTo() für kubische Bézier-Kurven mit zwei Kontrollpunkten erzeugt. Die Kontrollpunkte dienen quasi als ›Hebel‹, mit denen Weite und Richtung einer Kurve gesteuert werden können.

Die Methoden erwarten folgende Argumente:

quadraticCurveTo()
• x- und y-Koordinate des Kontrollpunktes
• x- und y-Koordinate des Endpunktes

bezierCurveTo()
• x- und y-Koordinate des ersten Kontrollpunktes
• x- und y-Koordinate des zweiten Kontrollpunktes
• x- und y-Koordinate des Endpunktes

Die beiden folgenden Beispiele bestehen aus jeweils zwei aneinander anschließenden Bézier-Kurven, bei denen der erste Abschnitt der Kurve 100px nach rechts und 0px nach unten verläuft. Der zweite Abschnitt verläuft 30px nach rechts und 20px nach unten. Um einen harmonischen Übergang zwischen den Abschnitten zu gewährleisten, ist der erste Kontrollpunkt des zweiten Abschnittes der am Endpunkt des ersten Abschnittes (= Ausgangspunkt des zweiten Abschnittes) gespiegelte letzte Kontrollpunkt des ersten Abschnittes (Punktspiegelung).

Im ersten Beispiel (quadratische Kurve) liegt der Kontrollpunkt des ersten Abschnitts 40px rechts und 45px unterhalb des Ausganspunktes 20,50.

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
      // Quadratische Bézier-Kurve

      var x1 = 20;
      var y1 = 50;
      var x2 = x1 + 100;
      var y2 = y1 + 0;
      var x3 = x2 + 30;
      var y3 = y2 + 20;
      var xc1 = x1 + 40;
      var yc1 = y1 + 45;
      var xc2 = x2 + (x2 - xc1);
      var yc2 = y2 + (y2 - yc1);

      cx.fillStyle = 'cyan';
      cx.strokeStyle = 'red';
      cx.lineWidth = 2;
      cx.beginPath();
      cx.moveTo(x1,y1);
      cx.quadraticCurveTo(xc1,yc1,x2,y2);
      cx.quadraticCurveTo(xc2,yc2,x3,y3);
      cx.fill();
      cx.stroke();

      cx.fillStyle = 'black';

      cx.beginPath();
      cx.arc(x1,y1,2,0,2 * Math.PI);
      cx.fill();
      cx.stroke();

      cx.beginPath();
      cx.arc(x2,y2,2,0,2 * Math.PI);
      cx.fill();
      cx.stroke();

      cx.beginPath();
      cx.arc(x3,y3,2,0,2 * Math.PI);
      cx.fill();
      cx.stroke();

      cx.strokeStyle = 'cyan';

      cx.beginPath();
      cx.arc(xc1,yc1,2,0,2 * Math.PI);
      cx.fill();
      cx.stroke();

      cx.beginPath();
      cx.arc(xc2,yc2,2,0,2 * Math.PI);
      cx.fill();
      cx.stroke();

      cx.strokeStyle = 'grey';
      cx.lineWidth = 1;

      cx.beginPath();
      cx.moveTo(x1,y1);
      cx.lineTo(xc1,yc1);
      cx.lineTo(xc2,yc2);
      cx.lineTo(x3,y3);
      cx.stroke();

      // Kubische Bézier-Kurve

      x1 = 20;
      y1 = 120;
      x2 = x1 + 100;
      y2 = y1 + 0;
      x3 = x2 + 30;
      y3 = y2 + 20;
      xc1 = x1 + 50;
      yc1 = y1 + 50;
      xc2 = x1 + 90;
      yc2 = y1 - 20;
      xc3 = x2 + (x2 - xc2);
      yc3 = y2 + (y2 - yc2);
      xc4 = x2 + 50;
      yc4 = y2 + 0;

      cx.fillStyle = 'orange';
      cx.strokeStyle = 'red';
      cx.lineWidth = 2;
      cx.beginPath();
      cx.moveTo(x1,y1);
      cx.bezierCurveTo(xc1,yc1,xc2,yc2,x2,y2);
      cx.bezierCurveTo(xc3,yc3,xc4,yc4,x3,y3);
      cx.fill();
      cx.stroke();

      cx.fillStyle = 'black';

      cx.beginPath();
      cx.arc(x1,y1,2,0,2 * Math.PI);
      cx.fill();
      cx.stroke();

      cx.beginPath();
      cx.arc(x2,y2,2,0,2 * Math.PI);
      cx.fill();
      cx.stroke();

      cx.beginPath();
      cx.arc(x3,y3,2,0,2 * Math.PI);
      cx.fill();
      cx.stroke();

      cx.strokeStyle = 'orange';

      cx.beginPath();
      cx.arc(xc1,yc1,2,0,2 * Math.PI);
      cx.fill();
      cx.stroke();

      cx.beginPath();
      cx.arc(xc2,yc2,2,0,2 * Math.PI);
      cx.fill();
      cx.stroke();

      cx.beginPath();
      cx.arc(xc3,yc3,2,0,2 * Math.PI);
      cx.fill();
      cx.stroke();

      cx.beginPath();
      cx.arc(xc4,yc4,2,0,2 * Math.PI);
      cx.fill();
      cx.stroke();

      cx.strokeStyle = 'grey';
      cx.lineWidth = 1;

      cx.beginPath();
      cx.moveTo(x1,y1);
      cx.lineTo(xc1,yc1);
      cx.moveTo(xc2,yc2);
      cx.lineTo(xc3,yc3);
      cx.moveTo(xc4,yc4);
      cx.lineTo(x3,y3);
      cx.stroke();

Im zweiten Beispiel (kubische Kurve) liegt der erste Kontrollpunkt des ersten Abschnittes 50px rechts und 50px unterhalb des Ausgangspunktes 20,120. Der zweite Kontrollpunkt liegt 90px rechts und 20px oberhalb des Ausgangspunktes 20,120, bezieht sich aber auf den Endpunkt des ersten Kurvenabschnittes (120,120). Der zweite Kontrollpunkt des zweiten Abschnittes befindet sich 50px rechts und 0px unterhalb des Ausgangspunktes des zweiten Abschnittes (= Endpunkt des ersten Kurvenabschnittes).

Farbverläufe #

Statt einer homogenen Färbung der Konturlinie oder der Füllung eines Pfades können zwei Arten von Farbverläufen angewendet werden. Einerseits lineare Farbverläufe, die sich zwischen zwei parallelen Geraden erstrecken. Andererseits radiale Farbverläufe, die sich von einem zentralen Punkt ausdehnen. Innerhalb des Farbverlaufs lassen sich mit addColorStop() an beliebigen Positionen Farben festlegen.

Farbverläufe werden zunächst als Objekt deklariert und können anschließend an Stelle einer Farbe angegeben werden.

Lineare Farbverläufe #

Lineare Farbverläufe werden mit createLinearGradient() erzeugt, wobei die Methode vier Argumente erwartet:

• x- und y-Koordinate des Ausgangspunktes des Farbverlaufs
• x- und y-Koordinate des Endpunktes des Farbverlaufs

Welche Farben an welcher Position des Farbverlaufs dargestellt werden sollen, wird mit addColorStop() festgelegt, wobei die Methode zwei Argumente erwartet:

• die Position der gewünschten Farbe in einem Bereich zwischen 0 (Anfang) und 1 (Ende)
• den Wert der Farbe

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
      var grd1 = cx.createLinearGradient(40,40, 310,140);
      grd1.addColorStop(0.3, 'blue');
      grd1.addColorStop(0.5, 'white');
      grd1.addColorStop(0.7, 'red');
      grd1.addColorStop(1.0, 'hsla(0,100%,50%,0)');

      var grd2 = cx.createLinearGradient(0,0, 0,180);
      grd2.addColorStop(0.0, 'yellow');
      grd2.addColorStop(1.0, 'green');

      cx.fillStyle = grd1;
      cx.strokeStyle = grd2;
      cx.lineWidth = 8;
      cx.fillRect(20,20,310,140);
      cx.strokeRect(20,20,310,140);

      cx.strokeStyle = 'black';
      cx.lineWidth = 1;

      cx.moveTo(40,40);
      cx.lineTo(310,140);
      cx.stroke();

Der Farbverlauf erstreckt sich vom Ausgangspunkt zum Endpunkt. Punkte jenseits des ersten und letzten Colorstops erhalten die Farbe des jeweiligen Colorstops.

Punkte rechtwinklig zu der Linie zwischen Ausgangspunkt und Endpunkt erhalten die Farbe entsprechend der Position des jeweiligen Punktes auf der Linie.

Radiale Farbverläufe #

Radiale Farbverläufe werden mit createRadialGradient() erzeugt, wobei die Methode sechs Argumente erwartet:

• x- und y-Koordinate des Mittelpunktes des Ausgangskreises des Farbverlaufs
• Radius des Ausgangskreises des Farbverlaufs
• x- und y-Koordinate des Mittelpunktes des Endkreises des Farbverlaufs
• Radius des Endkreises des Farbverlaufs

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
      var grd1 = cx.createRadialGradient(100,15,120, 175,90,10);
      grd1.addColorStop(0.3, 'blue');
      grd1.addColorStop(0.5, 'white');
      grd1.addColorStop(0.7, 'red');
      grd1.addColorStop(1.0, 'hsla(0,100%,50%,0)');

      var grd2 = cx.createLinearGradient(0,0, 0,180);
      grd2.addColorStop(0.0, 'yellow');
      grd2.addColorStop(1.0, 'green');

      cx.fillStyle = grd1;
      cx.strokeStyle = grd2;
      cx.lineWidth = 8;
      cx.fillRect(20,20,310,140);
      cx.strokeRect(20,20,310,140);

      cx.strokeStyle = 'black';
      cx.lineWidth = 1;

      cx.beginPath();
      cx.arc(100,15,120,0,2 * Math.PI);
      cx.stroke();
      cx.beginPath();
      cx.arc(175,90,10,0,2 * Math.PI);
      cx.stroke();

      cx.moveTo(100,15);
      cx.lineTo(175,90);
      cx.stroke();

Der Farbverlauf erstreckt sich entlang einer gedachten Linie zwischen den Mittelpunkten von Ausgangskreis und Endkreis. Punkte innerhalb des inneren und außerhalb des äußeren Colorstops erhalten die Farbe des jeweiligen Colorstops.

Die Punkte eines beliebigen Kreises mit dem Mittelpunkt M, der sich auf der erwähnten Verbindungslinie befindet, haben die gleiche Farbe wie der Schnittpunkt dieses Kreises mit der Verbindungslinie.

Schatten #

Schatten können über folgende Eigenschaften definiert werden:

  • shadowColor die Farbe des Schattens
  • shadowOffsetX der Versatz des Schattens auf der x-Achse
  • shadowOffsetY der Versatz des Schattens auf der y-Achse
  • shadowBlur der Grad der Weichzeichnung des Schattens

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
12
      cx.shadowColor = 'black';
      cx.shadowOffsetX = 5;
      cx.shadowOffsetY = 5;
      cx.shadowBlur = 4;

      cx.lineWidth = 10;
      cx.strokeStyle = 'green';

      cx.beginPath();
      cx.moveTo(50,90);
      cx.lineTo(300,90);
      cx.stroke();

Der Code erzeugt folgende Grafik:

Transformationen #

Analog zu den 2D-Transformationen in CSS3 und SVG lassen sich auch die einzelnen Komponenten der Grafik des <canvas>-Elements manipulieren. Transformationen gelten so lange, bis sie zurückgesetzt werden, daher addieren sich auf einander folgende Transformationen.

Versatz #

Mit Hilfe der Transformation translate() kann ein Objekt entlang der Achsen des Koordinatensystems verschoben werden. Die Methode erwartet zwei Argumente:

• x- und y-Differenz des Versatzes

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
12
      cx.strokeStyle = 'yellow';
      cx.fillStyle = 'red';
      cx.lineWidth = 4;
      cx.fillRect(82,42,186,96);
      cx.strokeRect(80,40,190,100);

      cx.globalAlpha = .5;
      cx.translate(50,20);

      cx.fillStyle = 'blue';
      cx.fillRect(82,42,186,96);
      cx.strokeRect(80,40,190,100);

Der Code erzeugt folgende Grafik:

Skalierung #

Mit Hilfe der Transformation scale() kann ein Objekt skaliert (vergrößert oder verkleinert) werden, wobei sich der Bezugspunkt der Skalierung am Nullpunkt des Koordinatensystems befindet (links oben). Die Methode erwartet zwei Argumente:

• x- und y-Faktor der Skalierung

Die ursprünglichen Dimensionen werden mit dem angegebenen Faktor multipliziert. Eine Linie mit einer ursprünglichen Länge von 100 Pixeln besitzt daher z. B. bei einem Faktor von 2.5 eine Länge von 250 Pixeln.

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
12
      cx.strokeStyle = 'yellow';
      cx.fillStyle = 'red';
      cx.lineWidth = 4;
      cx.fillRect(82,42,186,96);
      cx.strokeRect(80,40,190,100);

      cx.globalAlpha = .5;
      cx.scale(1.2,.5);

      cx.fillStyle = 'blue';
      cx.fillRect(82,42,186,96);
      cx.strokeRect(80,40,190,100);

Der Code erzeugt folgende Grafik:

Drehung #

Mit Hilfe der Transformation rotate() kann ein Objekt um den Nullpunkt des Koordinatensystems gedreht werden. Die Methode erwartet als Argument den Winkel der Drehung.

Der Winkel wird nicht in Grad, sondern in Radiant (Bogenmaß) angegeben, wobei der Vollkreis von 360° im Bogenmaß 2π rad entspricht. Möchte man Grad in Radiant umrechnen, so multipliziert man den Gradwert daher mit (π / 180).

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
12
      cx.strokeStyle = 'yellow';
      cx.fillStyle = 'red';
      cx.lineWidth = 4;
      cx.fillRect(82,42,186,96);
      cx.strokeRect(80,40,190,100);

      cx.globalAlpha = .5;
      cx.rotate(20 * Math.PI / 180);

      cx.fillStyle = 'blue';
      cx.fillRect(82,42,186,96);
      cx.strokeRect(80,40,190,100);

Der Code erzeugt folgende Grafik:

Möchte man eine Komponente nicht um den Nullpunkt, sondern um einen anderen Punkt drehen, so kann man sich folgender Funktion bedienen. Im Beispiel soll der Drehpunkt im Mittelpunkt der Grafik, also bei 175,90 liegen.

Dazu muss die Komponente vor der Drehung mit translate() versetzt werden. Die Differenz des Versatzes wird mit der Funktion transXY() aus den Koordinaten des Drehpunktes und dem gewünschten Drehwinkel ermittelt.

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
      function transXY(x1,y1,ang) {
        var r = Math.sqrt(Math.pow(x1,2) + Math.pow(y1,2));
        var ang1 = Math.asin(y1 / r);
        var ang2 = ang + ang1;
        var x2 = Math.cos(ang2) * r;
        var y2 = Math.sin(ang2) * r;
        return [x1 - x2, y1 - y2];
      }

      cx.strokeStyle = 'yellow';
      cx.fillStyle = 'red';
      cx.lineWidth = 4;
      cx.fillRect(82,42,186,96);
      cx.strokeRect(80,40,190,100);

      cx.globalAlpha = .5;
      var ang = 20 * Math.PI / 180;
      var t = transXY(175,90,ang);
      cx.translate(t[0],t[1]);
      cx.rotate(ang);

      cx.fillStyle = 'blue';
      cx.fillRect(82,42,186,96);
      cx.strokeRect(80,40,190,100);

Der Code erzeugt folgende Grafik:

Neigung #

Mit der Methode transform() lassen sich Skalierung, Versatz und auch die Neigung einer Komponente mit einer einzigen Anweisung festlegen. Die Drehung lässt sich so nicht beeinflussen. Diese Methode erwartet sechs Argumente:

• x-Faktor der Skalierung
• Winkel der Neigung in x-Richtung (in Radiant)
• Winkel der Neigung in y-Richtung (in Radiant)
• y-Faktor der Skalierung
• x-Differenz des Versatzes
• y-Differenz des Versatzes

Während sich aufeinander folgende Transformationen mit transform() addieren, können mit setTransform() Transformationen mit absoluten Werten durchgeführt werden, so als hätten zuvor noch keine anderen Transformationen stattgefunden. Auf diese Weise lassen sich auch alle bisherigen Transformationen mit setTransform(1,0,0,1,0,0) zurücksetzen.

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
      cx.globalAlpha = 1;

      cx.strokeStyle = 'yellow';
      cx.lineWidth = 4;
      cx.fillStyle = 'red';
      cx.fillRect(82,42,186,96);
      cx.strokeRect(80,40,190,100);

      cx.globalAlpha = .5;
      cx.transform(1, 0, Math.tan(-20 * Math.PI / 180), 1, 0, 0);

      cx.fillStyle = 'blue';
      cx.fillRect(82,42,186,96);
      cx.strokeRect(80,40,190,100);

      cx.setTransform(1, 0, 0, 1, 0, 0);
      cx.transform(1, 0, Math.tan(-40 * Math.PI / 180), 1, 0, 0);

      cx.fillStyle = 'green';
      cx.fillRect(82,42,186,96);
      cx.strokeRect(80,40,190,100);

      cx.setTransform(1, 0, 0, 1, 0, 0);

      cx.globalAlpha = 1;
      cx.strokeStyle = 'black';
      cx.lineWidth = 1;
      cx.moveTo(80,0);
      cx.lineTo(80,140);
      cx.moveTo(80,0);
      cx.lineTo(29,140);
      cx.moveTo(80,0);
      cx.lineTo(-37,140);
      cx.stroke();

Für die Neigung in x-Richtung muss lediglich der gewünschte Winkel als drittes Argument angegeben werden. Der Ursprung des Winkels befindet sich an Position x,0, wobei x der horizontalen Ausgangsposition des folgenden Pfades entspricht (im Beispiel das zu zeichnende Rechteck). Die Werte für die Skalierung müssen 1 betragen, die Werte für den Versatz 0.

Grafikdateien #

Mit drawImage() können Grafikdateien in die Grafik des <canvas>-Elements eingefügt werden. Die Methode erwartet mindestens drei Argumente:

• das Grafikobjekt
• die x- und y-Koordinate der gewünschten Position

Zusätzlich können bei Bedarf als viertes und fünftes Argument die x- und y-Dimensionen einer Skalierung der Ursprungsgrafik angegeben werden.

Das hier gewählte Verfahren mit der Methode onload soll sicherstellen, dass die gewünschte Grafik schon geladen wurde, bevor sie eingefügt wird, da es sonst bei einigen Browsern zu Darstellungsfehlern kommen kann.

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
      var logo1 = new Image();
      logo1.src = 'http://www.decocode.de/img/sunset.jpg';
      logo1.onload = function() {
        cx.drawImage(logo1, 40,30);
      };

      var logo2 = new Image();
      logo2.src = 'http://www.decocode.de/img/HTML5_Logo_256.png';
      logo2.onload = function() {
        cx.drawImage(logo2, 190,20, 150,150);
      };

Der Code erzeugt folgende Grafik:

zur Bildquelle

Text #

Auf diese Weise lassen sich auch komplexere Effekte realisieren:

Hier der Quelltext dieser Grafik:

Quelltext auswählen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
      var cv = document.getElementById('test');
      cv.width = 580;
      cv.height = 215;
      var cx = cv.getContext('2d');

      var chars = 'decocode';
      var x = 36;
      var y = 120;

      var grd1 = cx.createLinearGradient(0,0, 0,120);
      grd1.addColorStop(0.0, '#66aaff');
      grd1.addColorStop(1.0, '#001144');

      var grd2 = cx.createLinearGradient(0,0, 0,120);
      grd2.addColorStop(0.0, '#fff6bf');
      grd2.addColorStop(0.7, '#ff9900');
      grd2.addColorStop(1.0, '#993300');

      var grd3 = cx.createLinearGradient(0,125, 0,190);
      grd3.addColorStop(0.0, 'hsla(0,0%,0%,.7)');
      grd3.addColorStop(1.0, 'hsla(0,0%,0%,1)');

      cx.fillStyle = 'black';

      cx.fillRect(0,0, 580,215);  // Hintergrund

      cx.fillStyle = grd1;
      cx.strokeStyle = grd2;
      cx.lineWidth = 8;
      cx.font = 'bold 110px Ubuntu';
      cx.setTransform(1,0,0,-1,0,255);

      cx.strokeText(chars, x,y);  // kopfüber, Linie
      cx.fillText(chars, x,y);  // kopfüber, Füllung

      cx.setTransform(1,0,0,1,0,0);
      cx.fillStyle = grd3;

      cx.fillRect(0,125, 580,90);  // Schatten

      cx.shadowColor = 'red';
      cx.shadowBlur = 20;

      cx.strokeText(chars, x,y);  // aufrecht, Linie

      cx.shadowBlur = 0;
      cx.fillStyle = grd1;

      cx.fillText(chars, x,y);  // aufrecht, Füllung