W3docs

Canvas-Zeichnung

Schritt für Schritt auf dem HTML-Canvas mit JavaScript zeichnen: Rechtecke, Striche, Pfade, Linien, Kreise und Bögen mit ausführbaren Codebeispielen.

Das <canvas>-Element ist eine leere, transparente Zeichenfläche. Ohne weiteres zeigt es nichts an — es lässt sich weder mit HTML noch mit CSS darauf zeichnen. Alle Zeichenoperationen auf einem Canvas werden mit JavaScript durchgeführt, über ein Objekt namens Rendering-Kontext. Diese Seite führt Schritt für Schritt durch die wichtigsten 2D-Zeichenoperationen: gefüllte Rechtecke, Umrisslinien, freie Pfade sowie Kreise und Bögen.

Falls noch kein <canvas>-Element erstellt wurde, empfiehlt sich zunächst die Lektüre des HTML-<canvas>-Tags und der Canvas-Einführung.

Das Canvas-Koordinatensystem

Jede Zeichenmethode arbeitet mit Pixelkoordinaten. Das Canvas-Raster hat seinen Ursprung (0, 0) in der oberen linken Ecke. Die x-Achse wächst nach rechts und die y-Achse wächst nach unten (zu beachten: y nimmt beim Bewegen nach unten zu, anders als in einem mathematischen Koordinatensystem). Der Punkt (0, 0) ist somit das Pixel oben links, und (width, height) ist die untere rechte Ecke.

Eine ausführliche Betrachtung des Rasters findet sich unter Canvas-Koordinaten.

1. Das Canvas-Element finden

Zunächst wird eine Referenz auf das <canvas>-Element im DOM mit getElementById() geholt:

const canvas = document.getElementById("canvas");

2. Den 2D-Rendering-Kontext abrufen

Die Methode getContext() liefert einen Zeichenkontext. Mit dem Argument "2d" wird ein CanvasRenderingContext2D-object zurückgegeben, das alle Eigenschaften und Methoden zum Zeichnen enthält:

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

Die Variable ctx (Kurzform für „context") ist das Objekt, auf dem alle Zeichenbefehle aufgerufen werden.

getContext() gibt null zurück, wenn der Browser den angeforderten Kontext nicht bereitstellen kann (beispielsweise wenn "2d" falsch geschrieben wurde oder das Element kein <canvas> ist). Es empfiehlt sich, dies vor dem Zeichnen abzufangen, damit ein fehlender Kontext still scheitert, anstatt in der nächsten Zeile einen Fehler auszulösen:

const ctx = canvas.getContext("2d");
if (!ctx) return; // bail out if the 2D context is unavailable; only valid inside a function

In einem einfachen inline <script> (das kein Funktionskörper ist) wird das Zeichnen stattdessen in if (ctx) { ... } eingeschlossen, wie es die folgenden Beispiele zeigen.

3. Ein gefülltes Rechteck zeichnen

Die Eigenschaft fillStyle legt die Farbe fest, mit der Formen gefüllt werden. Sie akzeptiert jede CSS-Farbe, ein Muster oder einen Farbverlauf:

ctx.fillStyle = "#1c87c9";

Anschließend zeichnet fillRect(x, y, width, height) ein gefülltes Rechteck. Die ersten beiden Parameter sind die x- und y-Koordinaten der oberen linken Ecke, gefolgt von Breite und Höhe in Pixeln:

ctx.fillRect(0, 0, 230, 130);

Beispiel: ein gefülltes Rechteck zeichnen:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the document</title>
  </head>
  <body>
    <canvas id="canvas" width="250" height="150" role="img"
            aria-label="A blue filled rectangle" style="border:1px solid #dddddd;">
      Your browser does not support the canvas element.
    </canvas>
    <script>
      const canvas = document.getElementById("canvas");
      const ctx = canvas.getContext("2d");
      if (ctx) {
        // only draw when the 2D context is available
        ctx.fillStyle = "#1c87c9";
        ctx.fillRect(0, 0, 230, 130);
      }
    </script>
  </body>
</html>

Den Canvas barrierefrei machen

Was auf einem Canvas gezeichnet wird, sind reine Pixel — es gibt keine Struktur, deshalb können Screenreader es nicht lesen. Es gibt nichts anzukündigen, und Tastaturnutzer können auf nichts innerhalb des Canvas zugreifen. Assistive Technologie erhält auf zwei Wegen eine Textalternative:

  • Fallback-Inhalt wird zwischen den <canvas>-Tags platziert. Browser, die Canvas rendern, ignorieren ihn; assistive Technologie (und sehr alte Browser) nutzen ihn stattdessen. Hier sollte eine sinnvolle Beschreibung stehen – nicht „Ihr Browser unterstützt Canvas nicht."
  • role="img" zusammen mit aria-label beschreibt das fertige Bild als einzelnes Bild, genauso wie alt-Text ein <img> beschreibt.
<canvas id="chart" width="250" height="150" role="img"
        aria-label="Bar chart: sales doubled from Q1 to Q2.">
  A bar chart showing sales doubling from Q1 to Q2.
</canvas>

Für interaktive Inhalte (anklickbare Bereiche, Steuerelemente) müssen zusätzlich echte, fokussierbare DOM-Elemente bereitgestellt werden — Canvas allein ist nicht per Tastatur zugänglich.

Umrisse statt Füllung

Manchmal ist der Umriss einer Form gewünscht und keine durchgehende Füllung. Mit der Eigenschaft strokeStyle wird die Umrissfarbe festgelegt und strokeRect(x, y, width, height) zeichnet ein ungefülltes Rechteck. Die Eigenschaft lineWidth steuert die Dicke des Umrisses:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the document</title>
  </head>
  <body>
    <canvas id="canvas" width="250" height="150" role="img"
            aria-label="A blue rectangular outline" style="border:1px solid #dddddd;">
      A rectangle drawn as an outline.
    </canvas>
    <script>
      const canvas = document.getElementById("canvas");
      const ctx = canvas.getContext("2d");
      if (ctx) {
        ctx.strokeStyle = "#1c87c9";
        ctx.lineWidth = 4;
        ctx.strokeRect(20, 20, 200, 100);
      }
    </script>
  </body>
</html>
Result

Einen Teil des Canvas löschen

clearRect(x, y, width, height) löscht das angegebene Rechteck und macht es wieder vollständig transparent. Häufig wird es verwendet, um den gesamten Canvas vor dem Neuzeichnen zu leeren (zum Beispiel bei jedem Frame einer Animation):

// Erase everything on a 250 × 150 canvas
ctx.clearRect(0, 0, 250, 150);

Im nächsten Beispiel werden zwei blaue Rechtecke gezeichnet, und dann stanzt clearRect() ein transparentes Loch in die Mitte des Canvas — das rechte Rechteck ist dadurch teilweise gelöscht:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the document</title>
  </head>
  <body>
    <canvas id="canvas" width="250" height="150" role="img"
            aria-label="Two blue squares with a cleared rectangle cut out of the middle"
            style="border:1px solid #dddddd;">
      Two filled squares with a cleared region.
    </canvas>
    <script>
      const canvas = document.getElementById("canvas");
      const ctx = canvas.getContext("2d");
      if (ctx) {
        ctx.fillStyle = "#1c87c9";
        ctx.fillRect(20, 20, 100, 100);
        ctx.fillRect(130, 20, 100, 100);
        ctx.clearRect(90, 50, 70, 50); // erase a rectangle across both
      }
    </script>
  </body>
</html>
Result

Pfade zeichnen (Linien und freie Formen)

Rechtecke sind praktisch, aber die meisten Zeichnungen verwenden Pfade — Folgen von Punkten, die durch Linien oder Kurven verbunden sind. Ein Pfad wird mit einer kleinen Gruppe von Methoden aufgebaut und ist erst sichtbar, wenn stroke() (zum Zeichnen des Umrisses) oder fill() (zum Füllen der eingeschlossenen Fläche) aufgerufen wird.

MethodeFunktion
beginPath()Startet einen neuen, leeren Pfad.
moveTo(x, y)Setzt den „Stift" auf (x, y), ohne zu zeichnen.
lineTo(x, y)Fügt eine gerade Linie vom aktuellen Punkt zu (x, y) hinzu.
closePath()Zeichnet eine Linie zurück zum Startpunkt des Pfades.
stroke()Rendert den Pfad als Umrisslinie.
fill()Füllt die vom Pfad eingeschlossene Fläche.

Immer mit beginPath() beginnen

beginPath() löscht die Liste der Punkte, die der Kontext gerade verfolgt, und startet einen neuen Pfad. Das ist wichtig, weil der Canvas jeden Teilpfad speichert, bis er zurückgesetzt wird. Wird eine Form gezeichnet und dann ohne Aufruf von beginPath() mit dem Hinzufügen von Punkten für eine zweite Form begonnen, sind die alten Punkte noch im Pfad — das nächste stroke() oder fill() zeichnet also auch die erste Form erneut, oft mit der neuen Farbe und Linienbreite. Das führt dazu, dass Formen ineinander „bluten".

Faustregel: beginPath() vor jeder neuen Form aufrufen. Alle folgenden Beispiele beginnen damit.

Beispiel: eine einzelne Linie

Um eine Linie zu zeichnen, wird ein Pfad begonnen, der Stift zum Startpunkt bewegt, eine Linie zum Endpunkt gezogen und dann gestrichen:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the document</title>
  </head>
  <body>
    <canvas id="canvas" width="250" height="150" role="img"
            aria-label="A diagonal blue line" style="border:1px solid #dddddd;">
      A diagonal line drawn across the canvas.
    </canvas>
    <script>
      const canvas = document.getElementById("canvas");
      const ctx = canvas.getContext("2d");
      if (ctx) {
        ctx.beginPath();
        ctx.moveTo(20, 20);   // start point (x, y)
        ctx.lineTo(220, 120); // end point (x, y)
        ctx.lineWidth = 3;
        ctx.strokeStyle = "#1c87c9";
        ctx.stroke();         // make the line visible
      }
    </script>
  </body>
</html>
Result

Beispiel: ein Dreieck

Durch Verketten mehrerer lineTo()-Aufrufe entsteht eine mehrseitige Form. closePath() verbindet den letzten Punkt mit dem ersten, und fill() füllt die eingeschlossene Fläche:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the document</title>
  </head>
  <body>
    <canvas id="canvas" width="250" height="150" role="img"
            aria-label="A filled green triangle" style="border:1px solid #dddddd;">
      A filled green triangle.
    </canvas>
    <script>
      const canvas = document.getElementById("canvas");
      const ctx = canvas.getContext("2d");
      if (ctx) {
        ctx.beginPath();
        ctx.moveTo(125, 20);  // top vertex
        ctx.lineTo(220, 130); // bottom-right vertex
        ctx.lineTo(30, 130);  // bottom-left vertex
        ctx.closePath();      // back to the top vertex
        ctx.fillStyle = "#8ebf42";
        ctx.fill();
      }
    </script>
  </body>
</html>
Result

Kreise und Bögen zeichnen

Die Methode arc() zeichnet Kreise und gekrümmte Segmente:

ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise);
  • x, y — die Koordinaten des Mittelpunkts des Bogens.
  • radius — der Radius in Pixeln.
  • startAngle, endAngle — Start- und Endwinkel in Bogenmaß (nicht in Grad). Winkel 0 zeigt entlang der positiven x-Achse — gerade nach rechts, die 3-Uhr-Richtung — und standardmäßig nehmen Winkel im Uhrzeigersinn zu (da die Canvas-y-Achse nach unten wächst). Ein vollständiger Kreis beträgt 2 * Math.PI Bogenmaß.
  • counterclockwise — ein optionaler boolean-Wert. Mit true wird gegen den Uhrzeigersinn gezeichnet (Standard: false).

Da die Winkel in Bogenmaß angegeben werden, ist eine kleine Hilfsfunktion zur Umrechnung aus Grad praktisch:

function toRadians(degrees) {
  return degrees * Math.PI / 180;
}

// e.g. a quarter turn:
ctx.arc(125, 75, 50, 0, toRadians(90));

Damit entsprechen 90 Grad Math.PI / 2, 180 Grad Math.PI und 360 Grad 2 * Math.PI.

Ein vollständiger Kreis verläuft von 0 bis 2 * Math.PI. Wie bei jedem Pfad muss stroke() oder fill() aufgerufen werden, damit er sichtbar wird:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the document</title>
  </head>
  <body>
    <canvas id="canvas" width="250" height="150" role="img"
            aria-label="A blue filled circle" style="border:1px solid #dddddd;">
      A filled blue circle.
    </canvas>
    <script>
      const canvas = document.getElementById("canvas");
      const ctx = canvas.getContext("2d");
      if (ctx) {
        ctx.beginPath();
        ctx.arc(125, 75, 50, 0, 2 * Math.PI); // center (125,75), radius 50, full circle
        ctx.fillStyle = "#1c87c9";
        ctx.fill();
      }
    </script>
  </body>
</html>
Result

Um stattdessen einen Halbkreis zu zeichnen, wird der Bogen bei Math.PI Bogenmaß statt bei 2 * Math.PI beendet.

Wie es weitergeht

Sobald der Umgang mit Formen und Pfaden vertraut ist, lohnt sich ein Blick auf den Rest des Canvas-Tutorials:

Übungen

Übung
Was gibt der Aufruf von getContext('2d') auf einem Canvas-Element zurück?
Was gibt der Aufruf von getContext('2d') auf einem Canvas-Element zurück?
Übung
Warum sollte beginPath() vor dem Zeichnen jeder neuen Form aufgerufen werden?
Warum sollte beginPath() vor dem Zeichnen jeder neuen Form aufgerufen werden?
Was this page helpful?