Javascript API für HTML Canvas

HTML canvas ist eine Zeichenfläche, die im Markup mit einem leeren canvas-Tag angelegt und mit JavaScript on the Fly mit Formen, Linien, Farben und Verläufen gefüllt wird. Das Bild im Canvas ist eine Bitmap und keine Vektorgrafik wie SVG.

Beispiel, Example Malen im Canvas wie mit einem Pinsel

Im Canvas ist kein DOM

Die Zeichnung auf dem Canvas ist kein Teil des DOM, kann keine klickbaren Bereiche enthalten (keine Links oder Karten) und kann nicht wie SVG-Grafiken verlustfrei vergrößert und verkleinert werden.

Wozu also eine weitere Technik für Grafik, wo wir schon SVG haben? Das Canvas-API besticht durch hohe Performance, da es sich die einzelnen Elemente der Zeichnung nicht merken muss. Javascript für Canvas ist Hardware-beschleunigt und für schnelle Animationen, Spiele, Zeichnen und 3D-Animationen (WebGL) bestimmt.

Javascript canvas ist auch Bestandteil von ePub3, dem Standard für eBooks.

Freihandzeichnen und Malen mit Canvas

Malen auf dem Canvas wie mit einem dicken Pinsel ist ein kleines Beispiel für den Einsatz von Canvas. Das kleine Script zeichnet mit Kreisen. Das erkennt man sofort, wenn man den Finger oder die Maus schnell auf dem Canvas bewegt. Ebensogut würden kleine Linien funktionieren.

Einfaches HTML-Canvas-Element als Sketchpad
Zeichnen mit der Maus am Desktop oder dem Finger auf dem Touchscreen

toDataURL speichert die Pixel auf dem Canvas als Bildmap-Bild in PNG oder JPEG. Da hätten wir auch schon einen handfesten Grund für den Einsatz von Canvas: CSS und SVG können Bilder verändern, aber nicht als Bitmap speichern. Zeichnungen in einem Canvas werden mit canvas.toDataURL und canvas.toBlob gespeichert.

Canvas und Context

canvas.getContext("2d") erzeugt einen Kontext innerhalb des Canvas. Der Kontext ist ein Objekt mit Eigenschaften und Methoden, der Grafik innerhalb des Canvas rendert. Dieser Kontext kann entweder 2d oder webgl (3d) sein.

Javascript greift in drei Schritten auf den Canvas zu:

  1. Einen Verweis auf das canvas-Element erzeugen
    const canvas = document.getElementById ('canvas');
  2. Den Zeichen-Zustand oder context setzen
    const ctx = canvas.getContext ("2d");
  3. Ein Element (Form, Pfad, …) zeichnen
<canvas id="canvas" width="600" height="200">
</canvas>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext("2d");

ctx.fillStyle = "ivory";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
                    ▲         ▲                 ▲
                    │         │                 │
                    │         │                 Höhe der Füllung
                    │         Breite der Füllung
                    Ursprung des Canvas

ctx.fillStyle = "ivory";
ctx.fillRect(80, 40, 200, 120);

Der Ursprung der Zeichenfläche ist oben links an den Koordinaten (0,0). Bevor CSS oder Javascript die canvas-Fläche füllt, ist ein canvas-Element schwarz und transparent.

Canvas kennt keinerlei Objekte – Javascript sieht den Inhalt eines Canvas nur als eine Serie von Pixeln.

Linien und Pfade

Ein Pfad im Canvas beginnt mit beginPath () gefolgt von moveTo (x,y), um den Ausgangspunkt des Pfads zu setzen. Verbindungen von Punkt zu Punkt mit lineTo (), arcTo (), quadraticCurveTo () und bezierCurveTo () führen den Pfad weiter, bis er mit stroke gezeichnet oder mit fill gefüllt wird.

lineTo (x,y) gibt die Koordinaten der folgenden Punkte und erzeugt jeweils eine Linie vom Ausgangspunkt zum neuen Endpunkt.
Am Ende zeichnet stroke () die Kontur des Pfades. Und: Jeder Canvas kann immer nur einen aktiven Pfad haben.

ctx.beginPath();
ctx.moveTo(20, 180); // Feder zum Startpunkt
ctx.lineTo(40, 20); ctx.lineTo(140, 60); ctx.lineTo(180, 180);
ctx.stroke();

closePath () schließt den Pfad (2. Pfad, closePath wird vor stroke () aufgerufen) und erzeugt dabei eine Linie vom Ausgangspunkt zum Startpunkt des Pfades.

ctx.beginPath();
ctx.moveTo(220, 180); // Feder zum Startpunkt
ctx.lineTo(240, 20); ctx.lineTo(340, 60); ctx.lineTo(380, 180);
ctx.closePath();
ctx.stroke();

Der Pfad im Canvas muss nicht geschlossen werden, um den Pfad zu füllen. Aber fill () wird vor stroke () aufgerufen.

ctx.beginPath();
ctx.moveTo(420, 180); // Feder zum Startpunkt
ctx.lineTo(440, 20); ctx.lineTo(540, 60); ctx.lineTo(580, 180);
ctx.fill();
ctx.stroke();

Der Stroke – die Umrandung von Flächen oder Linien – auf den Canvas ist immer eine solide Linie. Gepunktete oder gestrichtelte Linien (dashed oder dotted) sind für den Javascript Canvas nicht vorgesehen.

Canvas-Text

Text, der mit Javascript in ein Canvas gesetzt wird, kann mit den üblichen Eigenschaften wie Schrift, Schriftgröße, Farbe beliebig positioniert werden. Anders als bei SVG-Texten verschmilzt der Text sofort zu Pixeln und ist für Screenreader und Suchmaschinen nicht lesbar.

Im Grunde genommen unterscheidet sich Text nicht von Pfaden und Formen und nutzt fillStyle und strokeStyle in gleicher Weise.

Text-Aktionen
Beschreibung
font
Schrift festlegen – und zwar genauso wie mit CSS. Default Schriftgröße ist 10px
textAlign
start (default), end, left, right, center
textBaseline
top, hanging, middle, alphabetic, ideographic, bottom – die Grundlinie für den Text
fillText(txt, x, y, [maxW])
Text als String rendern, wobei x, y nicht breiter als maxW werden dürfen
strokeText(txt, x, y, [maxW])
Text als String rendern, wobei x, y nicht breiter als maxW werden dürfen
measureText(text)
Gibt ein TextMetrik-Objekt der aktuellen Schrifteinstellung zurück zurück (z.B. width)

Und klar: CSS-Variablen funktionieren nicht im Javascript-Canvas.

const theString = "Text in einem Canvas ist nicht barrierefrei";
const left = 50;				
// Einfacher Text mit Default Einstellungen
ctx.fillStyle = 'var(--me-dim-background)'; 
ctx.fillText(theString, left,50);
		
// Zeichenkette mit Größen- und Schriftangabe
ctx.font = "44px Georgia"
ctx.fillText(theString, left,100);

Wie weit ein Text läuft, kann mit measureText festgestellt werden. Die Linie unter dem letzten Text sollte sich unter den gesamten Text erstrecken. Na ja … so einfach nicht.

// measureText um eine Linie unter den Text zu setzen
const textW = tctx.measureText(theString);
ctx.beginPath();
ctx.lineWidth = 5;
ctx.moveTo (left , 300);
ctx.lineTo (textW.width,300);
ctx.stroke();

Bézierkurven und quadratische Kurven

Wer mit Illustrator, Photoshop, SVG oder Inkscape Freisteller oder Grafiken erstellt, kennt Bezierkurven (die Feder in Photoshop). Pfade bestehen aus Linien, die an Knotenpunkte eine Biegung definieren und dabei von Knoten zu Knoten miteinander verbunden sind.

Mit stroke () zeichnet Javascript die Kontur des Pfades; fill () füllt den Pfad im Canvas.

  • Bézierkurven haben zwei Kontrollpunkte, um die Kurve festzulegen.
  • Quadratische Kurven haben einen Startpunkt und einen Kontrollpunkt am Endpunkt.
bezierCurveTo (cx1, cy1, cx2, cy2, end1, end2)
Zeichent eine Bézierlinie vom Ausgangspunkt der Feder mit zwei Kontrollpunkten an (cx1,cy1) und (cx2,cy2) am Endpunkt (end1,end2)
quadraticCurveTo (cx, cy, x, y)
Zeichnet eine quadratische Kurve am Ausgangpunkt mit einem Kontrollpunkt (cx,cy) am Endpunkt (x,y)
ctx.beginPath();
ctx.moveTo(50,120);
ctx.bezierCurveTo(50,20,200,20,200,70);
ctx.stroke();

ctx.beginPath();
ctx.moveTo(220,120);
ctx.quadraticCurveTo(220,20,380,70);

Die orange Linien zeigen die Kontrollpunkte an.

Path2D-API: SVG-Pfade in Canvas-Pfade umwandeln

Das Bild im Canvas entsteht durch eine Serie von Anweisungen, die Striche (stroke) und Füllungen (fill) erzeugen. Bei irregulären Formen wäre der Aufwand groß. Das Path2D-API erzeugt Pfade auf der Basis von SVG-Pfaden. Das erleichtert das Zeichnen von komplexen Pfaden im Canvas, denn für SVG können wir Grafik-Programme wie Inkscape und Adobe Illustrator einsetzen.

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext("2d");

if (ctx) {
	ctx.beginPath ();
	ctx.fillStyle = "navy";

	const p = new Path2D("m 220,80 c -21.669,0.0201 -36.033,15.666 -36.033,15.666 0,0 46.07373,
			-2.54273 39.592,22.562 -13.28399,29.57849 -58.748,-19.158 -98.765,-11.069 -40.017,
			...
			5.1211 -20.748,-21.947 -38.541,-30.152 -53.367,-30.138 z");
	ctx.fill(p);
}

Dabei ist die Umwandlung von SVG-Pfad zu Canvas-Pfad eigentlich einfach.

  • Grafikpfade mit großem M und großen C (Bezier) oder großen L (Line) ausgeben lassen. In Inkscape dazu Pfad-Ausgabe auf Absolut, ausgeben als Normales Inkscape
  • Pfad aus SVG: Wertepaar nach M übersetzen in moveTo(964.8,262.5), alle L-Paare übersetzen in lineTo(1158,315.5)
    M 964.8,262.5 L 1158,315 L 1206,396 L 1126,547 L 915,560 L 1013,723 L 1073,686 L 1072,635 L 1175,630
  • Bezier aus SVG: Wertepaar nach M übersetzen in moveTo(964.8,262.5), alle drei C-Paare übersetzen in ctx.bezierCurveTo( 936,342, 934,319, 956,308) – zusätzliches Komma nach jedem Wertepaar!
    M 940,350 C 936,342 934,319 956,308 C 974,299 989,308 996,326

Canvas zu Bitmap (PNG): toDataURL

toDataURL wandelt die Zeichnung in ein Bitmap-Bild um. PNG ist das sicherste Format.

let png = document.createElement("img");
png.src = canvas.toDataURL("image/png");
document.querySelector("#myimage").appendChild(png);

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAWgAAAFoCAYAAAB …
65WHVAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAggg==">

Die Zeichnungen in einem Canvas lassen sich nicht verlustfrei verkleinern und insbesondere nicht vergrößern wie SVG-Grafiken. So gesehen kann es Sinn machen, die Grafik im Canvas groß mit einem ausreichend hohen Potential für Bitmap-Grafiken anzulegen.

Ein HTML-canvas wird mit einfachem CSS responsive:

CSS für responsiven Canvas

.withcanvas canvas {
    display: block;
    width: 100%;
}
.withcanvas {
	position:relative;
	width:100%;
	margin-left: auto; margin-right: auto;
}

<div class="withcanvas" style="max-width:400px">
	<canvas id="rect" width="600" height="240"></canvas>
</div>

context.save (), context.restore (), context.clearRect ()

Jedes Canvas-Element enthält einen Zustand – den Kontext der Zeichenfläche.

context.save () speichert den Zustand mit den aktuellen Eigenschaften wie fillStyle, strokeStyle, Nullpunkt, line-width. context.restore () ruft einen gespeicherten Zustand ab. So entsteht eine History oder Protokoll des Zeichnungszustands.

Die Zustände können in einem Stack gespeichert werden. Der Drawing State verwaltet Eigenschaften des Canvas:

  • aktueller Wert von lineWidth, strokeStyle, fillStyle, lineCap usw.
  • aktuelle Transformations-Matrix
  • aktueller Clipping-Bereich

Drawing State ist keine History oder Protokoll, wie wir es z.B. von Photoshop kennen.

Der Drawing State konserviert nicht die Zeichnung, sondern Breite und Farbe von Konturen und die Füllung von Objekten!

Canvas Attribute und Funktionen

Javascript kann die HTML-Attribute des canvas-Tags programmatisch ändern – nicht anders als bei jedem HTML-Element. Das HTML canvas-Element selber ist Teil des DOMs – nicht aber sein Inhalt. Der Inhalt des canvas-Tags besteht aus Pixeln. Da gibt es keine Attribute, kein CSS.

Attribute/Funktionen

Beschreibung

width

Breite des canvas in Pixeln (default: 300)

height

Höhe des canvas in Pixeln (default: 150)

toDataURL(type)

Wandelt den Inhalt des canvas in ein statisches Bild um (image/png ist der Standard, den jeder Browser unterstützen muss)

getContext(ctxID)

Der Zeichen-Kontext des canvas-Elements

Anwendungen von Javascript canvas sind wahre Monster an Code: Ganz schön viel Arbeit.

p5*js Grafik-Library für Javascript, guter Einstieg für Grafiker und Webdesigner, ist einfach zu lernen, gut dokumentiert und ist in aktiver Entwicklung.

Javascript-Librarys für HTML Canvas vereinfachen die Programmierung: calebevans.me/projects/jcanvas

Suchen auf mediaevent.de