Formen zeichnen mit Canvas
Nachdem wir nun unsere Canvas-Umgebung eingerichtet haben, können wir uns mit den Details des Zeichnens auf dem Canvas befassen. Am Ende dieses Artikels haben Sie gelernt, wie man Rechtecke, Dreiecke, Linien, Bögen und Kurven zeichnet und sich mit einigen der grundlegenden Formen vertraut macht. Der Umgang mit Pfaden ist entscheidend, wenn Sie Objekte auf das Canvas zeichnen, und wir werden sehen, wie das gemacht werden kann.
Das Raster
Bevor wir mit dem Zeichnen beginnen können, müssen wir über das Canvas-Raster oder den Koordinatenraum sprechen. Unser HTML-Skelett von der vorhergehenden Seite hatte ein Canvas-Element mit einer Breite von 150 Pixeln und einer Höhe von 150 Pixeln.

Normalerweise entspricht eine Einheit im Raster einem Pixel auf dem Canvas. Der Ursprung dieses Rasters befindet sich in der oberen linken Ecke bei der Koordinate (0,0). Alle Elemente sind relativ zu diesem Ursprung platziert. So wird die Position der oberen linken Ecke des blauen Quadrats x Pixel von links und y Pixel von oben bei der Koordinate (x,y) sein. Später in diesem Tutorial werden wir sehen, wie wir den Ursprung an eine andere Position verschieben, das Raster drehen und sogar skalieren können, aber vorerst bleiben wir bei den Standardeinstellungen.
Rechtecke zeichnen
Im Gegensatz zu SVG unterstützt <canvas> nur zwei primitive Formen: Rechtecke und Pfade (Listen von Punkten, die durch Linien verbunden sind). Alle anderen Formen müssen durch die Kombination von einem oder mehreren Pfaden erstellt werden. Zum Glück stehen uns verschiedene Funktionen zum Zeichnen von Pfaden zur Verfügung, die es ermöglichen, sehr komplexe Formen zu komponieren.
Werfen wir zuerst einen Blick auf das Rechteck. Es gibt drei Funktionen, die Rechtecke auf dem Canvas zeichnen:
fillRect(x, y, width, height)-
Zeichnet ein gefülltes Rechteck.
strokeRect(x, y, width, height)-
Zeichnet eine rechteckige Umrandung.
clearRect(x, y, width, height)-
Löscht den angegebenen rechteckigen Bereich und macht ihn vollständig transparent.
Jede dieser drei Funktionen nimmt die gleichen Parameter. x und y legen die Position auf dem Canvas (relativ zum Ursprung) der oberen linken Ecke des Rechtecks fest. width und height geben die Größe des Rechtecks an.
Nachfolgend ist die draw()-Funktion von der vorhergehenden Seite, die jetzt diese drei Funktionen nutzt.
Beispiel für rechteckige Form
function draw() {
const canvas = document.getElementById("my-canvas");
const ctx = canvas.getContext("2d");
ctx.fillRect(25, 25, 100, 100);
ctx.clearRect(45, 45, 60, 60);
ctx.strokeRect(50, 50, 50, 50);
}
Die Ausgabe dieses Beispiels wird unten gezeigt.
Die fillRect()-Funktion zeichnet ein großes schwarzes Quadrat mit 100 Pixeln auf jeder Seite. Die clearRect()-Funktion löscht dann ein 60x60 Pixel großes Quadrat aus der Mitte, und dann wird strokeRect() aufgerufen, um eine rechteckige Umrandung von 50x50 Pixeln innerhalb des gelöscht Bereichs zu erzeugen (konzeptionell 50x50; tatsächlich sind es 52x52, wie im nächsten Abschnitt erläutert wird).
In den nächsten Seiten werden wir zwei alternative Methoden zu clearRect() sehen, und wir werden auch sehen, wie man die Farbe und den Stil der Umrandung der gezeichneten Formen ändert.
Im Gegensatz zu den Funktionen für Pfade, die wir im nächsten Abschnitt sehen, zeichnen alle drei Rechteckfunktionen sofort auf die Leinwand.
Unscharfe Kanten erkennbar?
Im obigen Rechteckbeispiel und in allen kommenden Beispielen werden Sie möglicherweise bemerken, dass die Kanten der Formen unschärfer erscheinen als die gleichwertigen Formen, die mit SVG oder CSS gezeichnet werden. Dies liegt nicht daran, dass die Canvas-API nicht in der Lage ist, scharfe Kanten zu zeichnen, sondern eher daran, wie das Canvas-Raster auf die tatsächlichen Bildschirm-Pixel abgebildet wird und auch daran, wie der Browser das Canvas skaliert. Wenn das obige Beispiel nicht ausreichend deutlich ist, skalieren wir das Canvas mit CSS:
<canvas id="canvas" width="15" height="15"></canvas>
#canvas {
width: 300px;
height: 300px;
}
function draw() {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.strokeRect(2, 2, 10, 10);
ctx.fillRect(7, 7, 1, 1);
}
In diesem Beispiel erstellen wir unser Canvas wirklich klein (15x15), verwenden dann aber CSS, um es auf 300x300 Pixel zu skalieren. Infolgedessen wird jedes Canvas-Pixel nun durch einen 20x20-Block von CSS-Pixeln dargestellt. Wir zeichnen ein umrandetes Rechteck von (2,2) bis (12,12) und ein gefülltes Rechteck von (7,7) bis (8,8). Es erscheint wirklich unscharf. Dies liegt daran, dass der Browser standardmäßig ein Glättungsalgorithmus verwendet, um die zusätzlichen Pixel zu interpolieren, wenn rasterbasierte Bilder skaliert werden. Das ist großartig für Fotos oder Canvas-Grafiken mit geschwungenen Kanten, aber nicht so gut für Formen mit geraden Kanten. Um dies zu beheben, können wir image-rendering auf pixelated setzen:
#canvas {
image-rendering: pixelated;
}
Jetzt, wenn der Browser das Canvas skaliert, wird die Pixelierung des Originals soweit wie möglich beibehalten.
Hinweis:
image-rendering: pixelated hat als Technik zur Erhaltung scharfer Kanten auch seine Probleme. Wenn CSS-Pixel nicht mit Geräte-Pixeln übereinstimmen (wenn der devicePixelRatio kein Ganzzahlwert ist), können bestimmte Pixel größer als andere gezeichnet werden, was zu einem ungleichmäßigen Erscheinungsbild führt. Dies ist jedoch kein leicht zu lösendes Problem, da es unmöglich ist, Geräte-Pixel genau zu füllen, wenn die CSS-Pixel nicht genau zu ihnen abgebildet werden können.
Aber nun wird ein anderes Problem sichtbar, das man auch schon im ursprünglichen Rechteckbeispiel beobachten konnte: das umrandete Rechteck ist nicht nur 2 Pixel breit statt 1, sondern erscheint auch grau statt in der Standardfarbe Schwarz. Dies liegt daran, wie die Koordinaten als Formgrenzen interpretiert werden.
Wenn man sich das Rasterdiagramm oben noch einmal ansieht, kann man sehen, dass Koordinaten wie 2 oder 12 keinen Pixel identifizieren, sondern vielmehr die Grenze zwischen zwei Pixeln. In den folgenden Bildern stellt das Raster das Canvas-Koordinatenraster dar. Die Quadrate zwischen den Linien sind tatsächliche Bildschirm-Pixel. In dem ersten Rasterbild unten wird ein Rechteck von (2,1) bis (5,5) gefüllt. Der gesamte Bereich dazwischen (hellrot) fällt auf die Pixelgrenzen, sodass das resultierende gefüllte Rechteck scharfe Kanten hat.

Betrachtet man einen Pfad von (3,1) bis (3,5) mit einer Linienstärke von 1.0, ergibt sich die Situation im zweiten Bild. Der tatsächlich zu füllende Bereich (dunkelblau) reicht nur zur Hälfte in die Pixel auf jeder Seite des Pfads hinein. Eine Annäherung daran muss dargestellt werden, was bedeutet, dass diese Pixel nur teilweise schattiert sind, und daher wird der gesamte Bereich (der hellblaue und der dunkelblaue Bereich) mit einer Farbe gefüllt, die nur halb so dunkel wie die eigentliche Schlagfarbe ist. Dies ist, was bei der 1.0 Breite Linie im strokeRect() Aufruf im obigen Rechteckbeispiel passiert.
Um dies zu beheben, müssen Sie sehr präzise bei der Pfaderstellung sein. Wenn Sie wissen, dass eine 1.0 Breite Linie sich eine halbe Einheit auf beiden Seiten des Pfades erstreckt, führt die Erstellung des Pfades von den Zentren der Pixel aus zu der Situation im dritten Bild – die 1.0 Linienbreite füllt am Ende eine einzelne vertikale Linie eines Pixels vollständig und präzise aus.
Hinweis: Beachten Sie, dass in unserem Beispiel der Y-Position der vertikalen Linie immer noch eine ganzzahlige Rasterlinienposition zugeordnet wurde – wenn dies nicht der Fall gewesen wäre, würden wir an den Endpunkten Pixel mit halber Abdeckung sehen.
Deshalb sagten wir früher, dass der strokeRect(50, 50, 50, 50) Aufruf im Rechteckbeispiel konzeptionell 50x50 ist, tatsächlich aber 52x52 ist. Der tatsächlich gefüllte Bereich für die Umrandung beginnt bei (49.5, 49.5) und endet bei (100.5, 100.5) und aufgrund der teilweise gefüllten Pixel ist der tatsächlich gefüllte Bereich von (49,49) bis (101,101), was 52x52 ist, und die Kanten sind 2 Pixel breit. Um eine solide 1-Pixel breite Umrandung, die genau 50x50 ist, zu erhalten, müssten Sie das Rechteck um die Dicke der Umrandung (1px) verkleinern und es um die Hälfte der Dicke (0.5px) verschieben:
function draw() {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.strokeRect(2.5, 2.5, 9, 9);
ctx.fillRect(7, 7, 1, 1);
}
Bei Linien mit gerader Breite ist jede Hälfte eine ganze Anzahl von Pixeln, sodass man einen Pfad haben möchte, der sich zwischen den Pixeln befindet (also (3,1) bis (3,5)), anstatt in der Mitte eines Pixels.
Obwohl es anfangs etwas mühsam ist, mit skalierbaren 2D-Grafiken zu arbeiten, stellt das Beachten des Pixelrasters und der Position von Pfaden sicher, dass Ihre Zeichnungen unabhängig von Skalierungen oder anderen Transformationen korrekt aussehen. Eine 1.0 breite vertikale Linie, die an der richtigen Position gezeichnet wird, wird beim Hochskalieren um den Faktor 2 zu einer scharfen 2-Pixel-Linie und erscheint an der richtigen Position.
Dieses Phänomen der teilweise gefüllten Pixel erstreckt sich auch auf Formen, die sich nicht mit dem Pixelraster ausrichten. Betrachten Sie zum Beispiel ein gedrehtes Rechteck (Sie werden lernen, wie es in den nächsten Abschnitt gezeichnet wird). Um zu sehen, wie es mit und ohne image-rendering: pixelated aussieht, haben wir zwei nebeneinander liegende Canvases und ein drittes in voller Größe gezeichnet, mit Rasterlinien:
function draw(canvasId) {
const canvas = document.getElementById(canvasId);
const ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(3, 2);
ctx.lineTo(9, 4.5);
ctx.lineTo(6.5, 10.5);
ctx.lineTo(0.5, 8);
ctx.closePath();
ctx.fill();
}
function drawFullScale() {
const canvas = document.getElementById("canvas3");
const ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(60, 40);
ctx.lineTo(180, 90);
ctx.lineTo(130, 210);
ctx.lineTo(10, 160);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = "lightgray";
for (let i = 0; i < 16; i++) {
ctx.moveTo(i * 20, 0);
ctx.lineTo(i * 20, 300);
ctx.moveTo(0, i * 20);
ctx.lineTo(300, i * 20);
ctx.stroke();
}
}
Wenn das Hochskalieren eines Bildes es unschärfer erscheinen lässt als beabsichtigt, dann würde das Herunterskalieren eines Bildes es schärfer erscheinen lassen. Wenn Sie beispielsweise möchten, dass ein Canvas auf dem Bildschirm als 300x150 Pixel erscheint, können Sie es als 600x300 Pixel erstellen und dann CSS verwenden, um es zu verkleinern. Dies ist besonders nützlich für Bildschirme mit hoher DPI (wie Apples Retina-Displays), bei denen ein CSS-Pixel durch mehrere Bildschirm-Pixel dargestellt wird, so dass, wenn man ein 300x150 Pixel Canvas genau gezeichnet wird, es nicht die gleiche Pixelauflösung wie andere Elemente auf der Seite haben wird.
Pfade zeichnen
Nun werfen wir einen Blick auf Pfade. Ein Pfad ist eine Liste von Punkten, verbunden durch Liniensegmente, die unterschiedliche Formen, gebogen oder nicht, mit unterschiedlicher Breite und Farbe haben können. Ein Pfad oder sogar ein Teil eines Pfades kann geschlossen sein. Um Formen mit Pfaden zu erstellen, gehen wir einige zusätzliche Schritte:
- Zuerst erstellen Sie den Pfad.
- Dann verwenden Sie Zeichenbefehle, um in den Pfad zu zeichnen.
- Sobald der Pfad erstellt wurde, können Sie den Pfad nachverfolgen oder füllen, um ihn zu rendern.
Hier sind die Funktionen, die bei diesen Schritten verwendet werden:
beginPath()-
Erstellt einen neuen Pfad. Nach der Erstellung werden zukünftige Zeichenbefehle auf den Pfad gerichtet und verwendet, um den Pfad zu erweitern.
- Pfad-Methoden
-
Methoden, um verschiedene Pfade für Objekte festzulegen.
closePath()-
Fügt dem Pfad eine gerade Linie hinzu, die zum Anfang des aktuellen Teilpfades führt.
stroke()-
Zeichnet die Form, indem ihre Umrandung nachgezogen wird.
fill()-
Zeichnet eine feste Form, indem der Inhalt des Pfads gefüllt wird.
Der erste Schritt zur Erstellung eines Pfades ist das Aufrufen von beginPath(). Intern werden Pfade als eine Liste von Teilpfaden (Linien, Bögen usw.) gespeichert, die zusammen eine Form bilden. Jedes Mal, wenn diese Methode aufgerufen wird, wird die Liste zurückgesetzt und wir können anfangen, neue Formen zu zeichnen.
Hinweis:
Wenn der aktuelle Pfad leer ist, z.B. unmittelbar nach dem Aufrufen von beginPath(), oder auf einem neu erstellten Canvas, wird der erste Konstruktionbefehl des Pfades immer als moveTo() behandelt, unabhängig davon, was er tatsächlich ist. Aus diesem Grund möchten Sie fast immer Ihre Startposition nach dem Zurücksetzen eines Pfades explizit setzen.
Der zweite Schritt ist das Aufrufen der Methoden, die tatsächlich die zu zeichnenden Pfade angeben. Diese werden wir in Kürze sehen.
Der dritte und optionale Schritt ist der Aufruf von closePath(). Diese Methode versucht die Form zu schließen, indem eine gerade Linie vom aktuellen Punkt zum Anfang gezeichnet wird. Wenn die Form bereits geschlossen ist oder nur ein Punkt in der Liste vorhanden ist, macht diese Funktion nichts.
Hinweis:
Wenn Sie fill() aufrufen, werden alle offenen Formen automatisch geschlossen, sodass Sie closePath() nicht aufrufen müssen. Dies ist nicht der Fall, wenn Sie stroke() aufrufen.
Dreieck zeichnen
Der Code zum Zeichnen eines Dreiecks würde beispielsweise so aussehen:
function draw() {
const canvas = document.getElementById("my-canvas");
const ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(75, 50);
ctx.lineTo(100, 75);
ctx.lineTo(100, 25);
ctx.fill();
}
Das Ergebnis sieht so aus:
Den Stift bewegen
Eine sehr nützliche Funktion, die eigentlich nichts zeichnet, aber Teil der oben beschriebenen Pfadliste wird, ist die moveTo()-Funktion. Sie können sich das wahrscheinlich am besten so vorstellen, als würden Sie einen Stift oder Bleistift vom einen Platz auf einem Blatt Papier an den nächsten Platz setzen.
moveTo(x, y)-
Bewegt den Stift zu den durch
xundyangegebenen Koordinaten.
Wenn das Canvas initialisiert oder beginPath() aufgerufen wird, möchten Sie in der Regel die moveTo()-Funktion verwenden, um den Startpunkt woanders hin zu verschieben. Wir könnten moveTo() auch verwenden, um nicht verbundene Pfade zu zeichnen. Schauen Sie sich das Smiley-Gesicht unten an.
Um dies selbst auszuprobieren, können Sie den unten stehenden Codeausschnitt verwenden. Kopieren Sie ihn einfach in die draw()-Funktion, die wir zuvor gesehen haben.
function draw() {
const canvas = document.getElementById("my-canvas");
const ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.arc(75, 75, 50, 0, Math.PI * 2, true); // Outer circle
ctx.moveTo(110, 75);
ctx.arc(75, 75, 35, 0, Math.PI, false); // Mouth (clockwise)
ctx.moveTo(65, 65);
ctx.arc(60, 65, 5, 0, Math.PI * 2, true); // Left eye
ctx.moveTo(95, 65);
ctx.arc(90, 65, 5, 0, Math.PI * 2, true); // Right eye
ctx.stroke();
}
Das Ergebnis sieht so aus:
Falls Sie die Verbindungslinien sehen möchten, können Sie die Zeilen entfernen, die moveTo() aufrufen.
Hinweis:
Um mehr über die arc()-Funktion zu erfahren, siehe den Abschnitt Bögen unten.
Linien
Zum Zeichnen gerader Linien verwenden Sie die lineTo()-Methode.
lineTo(x, y)-
Zeichnet eine Linie von der aktuellen Zeichenposition zur durch
xundyangegebenen Position.
Diese Methode nimmt zwei Argumente, x und y, die die Koordinaten des Endpunkts der Linie sind. Der Startpunkt hängt von zuvor gezeichneten Pfaden ab, bei denen der Endpunkt des vorherigen Pfades der Startpunkt für den folgenden ist, usw. Der Startpunkt kann auch durch die moveTo()-Methode geändert werden.
Das folgende Beispiel zeichnet zwei Dreiecke, eines gefüllt und eines umrandet.
function draw() {
const canvas = document.getElementById("my-canvas");
const ctx = canvas.getContext("2d");
// Filled triangle
ctx.beginPath();
ctx.moveTo(25, 25);
ctx.lineTo(105, 25);
ctx.lineTo(25, 105);
ctx.fill();
// Stroked triangle
ctx.beginPath();
ctx.moveTo(125, 125);
ctx.lineTo(125, 45);
ctx.lineTo(45, 125);
ctx.closePath();
ctx.stroke();
}
Dies beginnt mit dem Aufruf von beginPath(), um einen neuen Formpfad zu starten. Dann wird die moveTo() Methode verwendet, um den Startpunkt an die gewünschte Position zu verschieben. Darunter werden zwei Linien gezeichnet, die zwei Seiten des Dreiecks bilden.
Sie werden den Unterschied zwischen dem gefüllten und dem gestrichelten Dreieck bemerken. Dies liegt daran, dass Formen, wie oben erwähnt, beim Füllen automatisch geschlossen werden, jedoch nicht, wenn sie gestrichelt sind. Wenn wir das closePath() für das gestichelte Dreieck weggelassen hätten, wären nur zwei Linien gezeichnet worden, kein vollständiges Dreieck.
Bögen
Um Bögen oder Kreise zu zeichnen, verwenden wir die arc() oder arcTo()-Methoden.
arc(x, y, radius, startAngle, endAngle, counterclockwise)-
Zeichnet einen Bogen, der an der Position (x, y) zentriert ist, mit Radius r beginnend bei startAngle und endend bei endAngle, in der angegebenen Richtung, die durch counterclockwise angezeigt wird (standardmäßig im Uhrzeigersinn).
arcTo(x1, y1, x2, y2, radius)-
Zeichnet einen Bogen mit den angegebenen Steuerpunkten und Radius, verbunden durch eine gerade Linie mit dem vorherigen Punkt.
Betrachten wir die arc-Methode genauer, die sechs Parameter annimmt: x und y sind die Koordinaten des Mittelpunkts des Kreises, auf dem der Bogen gezeichnet werden soll. radius ist selbsterklärend. Die Parameter startAngle und endAngle definieren die Start- und Endpunkte des Bogens in Bogenmaß, entlang der Kreislinie gemessen. Diese werden von der x-Achse aus gemessen. Der Parameter counterclockwise ist ein Boolescher Wert, der, wenn true, den Bogen gegen den Uhrzeigersinn zeichnet; andernfalls wird der Bogen im Uhrzeigersinn gezeichnet.
Hinweis:
Winkel in der arc-Funktion werden in Bogenmaß und nicht in Grad gemessen. Um Grad in Bogenmaß umzurechnen, können Sie den folgenden JavaScript-Ausdruck verwenden: radians = (Math.PI/180)*degrees.
Das folgende Beispiel ist etwas komplexer als die, die wir oben gesehen haben. Es zeichnet 12 verschiedene Bögen, alle mit unterschiedlichen Winkeln und Füllungen.
Die beiden for Schleifen dienen dazu, durch die Reihen und Spalten von Bögen zu gehen. Für jeden Bogen starten wir einen neuen Pfad, indem wir beginPath() aufrufen. Im Code sind die Parameter für den Bogen jeweils in einer Variablen zur Klarheit, aber das würden Sie im echten Leben nicht unbedingt so machen.
Die x- und y-Koordinaten sollten klar genug sein. radius und startAngle sind festgelegt. Der endAngle beginnt in der ersten Spalte bei 180 Grad (halbkreisförmig) und wird in Schritten von 90 Grad erhöht, was in einem vollständigen Kreis in der letzten Spalte mündet.
Die Anweisung für den Parameter clockwise bewirkt, dass die erste und dritte Reihe im Uhrzeigersinn gezeichnet werden und die zweite und vierte Reihe gegen den Uhrzeigersinn. Schließlich bewirkt die if-Anweisung, dass die obere Hälfte gestrichelte Bögen sind und die untere Hälfte gefüllte Bögen.
Hinweis: Dieses Beispiel benötigt ein etwas größeres Canvas als die anderen auf dieser Seite: 150 x 200 Pixel.
function draw() {
const canvas = document.getElementById("my-canvas");
const ctx = canvas.getContext("2d");
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 3; j++) {
ctx.beginPath();
const x = 25 + j * 50; // x coordinate
const y = 25 + i * 50; // y coordinate
const radius = 20; // Arc radius
const startAngle = 0; // Starting point on circle
const endAngle = Math.PI + (Math.PI * j) / 2; // End point on circle
const counterclockwise = i % 2 !== 0; // clockwise or counterclockwise
ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise);
if (i > 1) {
ctx.fill();
} else {
ctx.stroke();
}
}
}
}
Bezier- und quadratische Kurven
Der nächste verfügbare Pfadtyp sind Bézier-Kurven, die sowohl in kubischer als auch in quadratischer Form vorliegen. Diese werden in der Regel verwendet, um komplexe organische Formen zu zeichnen.
quadraticCurveTo(cp1x, cp1y, x, y)-
Zeichnet eine quadratische Bézier-Kurve vom aktuellen Stiftposition bis zum Endpunkt, der durch
xundyangegeben ist, unter Verwendung des Kontrollpunkts, der durchcp1xundcp1yangegeben ist. bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)-
Zeichnet eine kubische Bézier-Kurve vom aktuellen Stiftposition bis zum Endpunkt, der durch
xundyangegeben ist, unter Verwendung der Kontrollpunkte, die durch (cp1x,cp1y) und (cp2x,cp2y) angegeben sind.
Der Unterschied zwischen diesen beiden liegt darin, dass eine quadratische Bézier-Kurve einen Start- und Endpunkt hat (blaue Punkte) und nur einen Kontrollpunkt (angegeben durch den roten Punkt), während eine kubische Bézier-Kurve zwei Kontrollpunkte verwendet.

Die Parameter x und y in beiden Methoden sind die Koordinaten des Endpunkts. cp1x und cp1y sind die Koordinaten des ersten Kontrollpunkts, und cp2x und cp2y sind die Koordinaten des zweiten Kontrollpunkts.
Die Verwendung quadratischer und kubischer Bézier-Kurven kann ziemlich herausfordernd sein, da wir, anders als in Vektorgrafik-Software wie Adobe Illustrator, keine direkte visuelle Rückmeldung darüber haben, was wir tun. Das macht es ziemlich schwer, komplexe Formen zu zeichnen. Im nächsten Beispiel werden wir einige einfache organische Formen zeichnen, aber wenn Sie die Zeit und vor allem die Geduld haben, können wesentlich komplexere Formen erstellt werden.
Es gibt nichts besonders Schwieriges in diesen Beispielen. In beiden Fällen sehen wir eine Abfolge von gezeichneten Kurven, die schließlich in einer vollständigen Form resultieren.
Quadratische Bezier-Kurven
Dieses Beispiel verwendet mehrere quadratische Bézier-Kurven, um eine Sprechblase zu zeichnen.
function draw() {
const canvas = document.getElementById("my-canvas");
const ctx = canvas.getContext("2d");
// Quadratic curves example
ctx.beginPath();
ctx.moveTo(75, 25);
ctx.quadraticCurveTo(25, 25, 25, 62.5);
ctx.quadraticCurveTo(25, 100, 50, 100);
ctx.quadraticCurveTo(50, 120, 30, 125);
ctx.quadraticCurveTo(60, 120, 65, 100);
ctx.quadraticCurveTo(125, 100, 125, 62.5);
ctx.quadraticCurveTo(125, 25, 75, 25);
ctx.stroke();
}
Kubische Bezier-Kurven
Dieses Beispiel zeichnet ein Herz mit kubischen Bézier-Kurven.
function draw() {
const canvas = document.getElementById("my-canvas");
const ctx = canvas.getContext("2d");
// Cubic curves example
ctx.beginPath();
ctx.moveTo(75, 40);
ctx.bezierCurveTo(75, 37, 70, 25, 50, 25);
ctx.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5);
ctx.bezierCurveTo(20, 80, 40, 102, 75, 120);
ctx.bezierCurveTo(110, 102, 130, 80, 130, 62.5);
ctx.bezierCurveTo(130, 62.5, 130, 25, 100, 25);
ctx.bezierCurveTo(85, 25, 75, 37, 75, 40);
ctx.fill();
}
Rechtecke
Zusätzlich zu den drei Methoden, die wir im Abschnitt Rechtecke zeichnen gesehen haben, welche rechteckige Formen direkt auf das Canvas zeichnen, gibt es auch die rect()-Methode, die einem aktuell offenen Pfad einen rechteckigen Pfad hinzufügt.
rect(x, y, width, height)-
Zeichnet ein Rechteck, dessen obere linke Ecke durch (
x,y) mit der angegebenenBreiteundHöhespezifiziert wird.
Vor der Ausführung dieser Methode wird die moveTo()-Methode automatisch mit den Parametern (x,y) aufgerufen. Mit anderen Worten, die aktuelle Stiftposition wird automatisch auf die Standardkoordinaten zurückgesetzt.
Kombinationen herstellen
Bis jetzt hat jedes Beispiel auf dieser Seite nur einen Typ von Pfadfunktionen pro Form verwendet. Es gibt jedoch keine Einschränkung, wie viele oder welche Arten von Pfaden Sie verwenden können, um eine Form zu erstellen. Schauen wir uns also in diesem letzten Beispiel an, wie wir alle Pfadfunktionen kombinieren können, um eine Reihe von sehr bekannten Spielcharakteren zu erstellen.
function draw() {
const canvas = document.getElementById("my-canvas");
const ctx = canvas.getContext("2d");
roundedRect(ctx, 12, 12, 184, 168, 15);
roundedRect(ctx, 19, 19, 170, 154, 9);
roundedRect(ctx, 53, 53, 49, 33, 10);
roundedRect(ctx, 53, 119, 49, 16, 6);
roundedRect(ctx, 135, 53, 49, 33, 10);
roundedRect(ctx, 135, 119, 25, 49, 10);
ctx.beginPath();
ctx.arc(37, 37, 13, Math.PI / 7, -Math.PI / 7, false);
ctx.lineTo(31, 37);
ctx.fill();
for (let i = 0; i < 8; i++) {
ctx.fillRect(51 + i * 16, 35, 4, 4);
}
for (let i = 0; i < 6; i++) {
ctx.fillRect(115, 51 + i * 16, 4, 4);
}
for (let i = 0; i < 8; i++) {
ctx.fillRect(51 + i * 16, 99, 4, 4);
}
ctx.beginPath();
ctx.moveTo(83, 116);
ctx.lineTo(83, 102);
ctx.bezierCurveTo(83, 94, 89, 88, 97, 88);
ctx.bezierCurveTo(105, 88, 111, 94, 111, 102);
ctx.lineTo(111, 116);
ctx.lineTo(106.333, 111.333);
ctx.lineTo(101.666, 116);
ctx.lineTo(97, 111.333);
ctx.lineTo(92.333, 116);
ctx.lineTo(87.666, 111.333);
ctx.lineTo(83, 116);
ctx.fill();
ctx.fillStyle = "white";
ctx.beginPath();
ctx.moveTo(91, 96);
ctx.bezierCurveTo(88, 96, 87, 99, 87, 101);
ctx.bezierCurveTo(87, 103, 88, 106, 91, 106);
ctx.bezierCurveTo(94, 106, 95, 103, 95, 101);
ctx.bezierCurveTo(95, 99, 94, 96, 91, 96);
ctx.moveTo(103, 96);
ctx.bezierCurveTo(100, 96, 99, 99, 99, 101);
ctx.bezierCurveTo(99, 103, 100, 106, 103, 106);
ctx.bezierCurveTo(106, 106, 107, 103, 107, 101);
ctx.bezierCurveTo(107, 99, 106, 96, 103, 96);
ctx.fill();
ctx.fillStyle = "black";
ctx.beginPath();
ctx.arc(101, 102, 2, 0, Math.PI * 2, true);
ctx.fill();
ctx.beginPath();
ctx.arc(89, 102, 2, 0, Math.PI * 2, true);
ctx.fill();
}
// A utility function to draw a rectangle with rounded corners.
function roundedRect(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x, y + radius);
ctx.arcTo(x, y + height, x + radius, y + height, radius);
ctx.arcTo(x + width, y + height, x + width, y + height - radius, radius);
ctx.arcTo(x + width, y, x + width - radius, y, radius);
ctx.arcTo(x, y, x, y + radius, radius);
ctx.stroke();
}
Das resultierende Bild sieht so aus:
Da dies tatsächlich überraschend einfach ist, gehen wir nicht im Detail darauf ein. Die wichtigsten Dinge, die zu beachten sind, sind die Verwendung der fillStyle-Eigenschaft im Zeichenkontext und die Verwendung einer Hilfsfunktion (in diesem Fall roundedRect()). Die Nutzung von Hilfsfunktionen für Zeichnungen, die Sie häufig machen, kann sehr hilfreich sein und die Menge des benötigten Codes sowie seine Komplexität reduzieren.
Am Ende dieses Tutorials werfen wir einen genaueren Blick auf fillStyle. Hier ändern wir lediglich die Füllfarbe der Pfade von der Standardfarbe Schwarz zu Weiß und dann wieder zurück.
Formen mit Löchern
Um eine Form mit einem Loch zu zeichnen, müssen wir das Loch in einer anderen Uhrzeigerrichtung zeichnen als die äußere Form. Entweder zeichnen wir die äußere Form im Uhrzeigersinn und die innere Form gegen den Uhrzeigersinn oder die äußere Form gegen den Uhrzeigersinn und die innere Form im Uhrzeigersinn.
function draw() {
const canvas = document.getElementById("my-canvas");
const ctx = canvas.getContext("2d");
ctx.beginPath();
// Outer shape clockwise ⟳
ctx.moveTo(0, 0);
ctx.lineTo(150, 0);
ctx.lineTo(75, 129.9);
// Inner shape counterclockwise ↺
ctx.moveTo(75, 20);
ctx.lineTo(50, 60);
ctx.lineTo(100, 60);
ctx.fill();
}
Im obigen Beispiel geht das äußere Dreieck im Uhrzeigersinn (von der oberen linken Ecke zur oberen rechten Ecke und endet am unteren Rand), und das innere Dreieck geht gegen den Uhrzeigersinn (vom oberen Rand zur unteren linken Ecke und endet rechts unten).
Path2D Objekte
Wie wir im letzten Beispiel gesehen haben, können eine Reihe von Pfaden und Zeichenbefehlen verwendet werden, um Objekte auf Ihrem Canvas zu zeichnen. Um den Code zu vereinfachen und die Leistung zu verbessern, ermöglicht das Path2D-Objekt, das in neueren Versionen der Browser verfügbar ist, Ihnen, diese Zeichenbefehle zu zwischenspeichern oder aufzuzeichnen. Sie können Ihre Pfade schnell abspielen.
Schauen wir uns an, wie wir ein Path2D-Objekt konstruieren können:
Path2D()-
Der
Path2D()-Konstruktor gibt ein neu instanziiertesPath2D-Objekt zurück, optional mit einem weiteren Pfad als Argument (erstellt eine Kopie) oder optional mit einem String, der aus SVG-Pfad-Daten besteht.
new Path2D(); // empty path object
new Path2D(path); // copy from another Path2D object
new Path2D(d); // path from SVG path data
Alle Pfadmethoden wie moveTo, rect, arc oder quadraticCurveTo, etc., die wir oben kennengelernt haben, sind auf Path2D-Objekten verfügbar.
Die Path2D API bietet auch eine Möglichkeit, Pfade mithilfe der addPath-Methode zu kombinieren. Dies kann nützlich sein, wenn Sie Objekte aus mehreren Komponenten erstellen möchten.
Path2D.addPath(path [, transform])-
Fügt dem aktuellen Pfad einen Pfad mit einer optionalen Transformationsmatrix hinzu.
Path2D Beispiel
In diesem Beispiel erstellen wir ein Rechteck und einen Kreis. Beide werden als Path2D-Objekt gespeichert, sodass sie später verfügbar sind. Mit der neuen Path2D API wurden mehrere Methoden aktualisiert, um optional ein Path2D-Objekt zu akzeptieren, das anstelle des aktuellen Pfades verwendet wird. Hier werden stroke und fill mit einem Pfad-Argument verwendet, um beide Objekte auf das Canvas zu zeichnen.
function draw() {
const canvas = document.getElementById("my-canvas");
const ctx = canvas.getContext("2d");
const rectangle = new Path2D();
rectangle.rect(10, 10, 50, 50);
const circle = new Path2D();
circle.arc(100, 35, 25, 0, 2 * Math.PI);
ctx.stroke(rectangle);
ctx.fill(circle);
}
Nutzung von SVG-Pfaden
Ein weiteres leistungsstarkes Merkmal der neuen Path2D Canvas-API ist die Verwendung von SVG-Pfaddaten, um Pfade auf Ihrem Canvas zu initialisieren. Dies könnte Ihnen ermöglichen Pfaddaten zu übermitteln und sowohl in SVG als auch in Canvas wiederzuverwenden.
Der Pfad wird bis zu Punkt (M10 10) bewegt und dann horizontal 80 Punkte nach rechts bewegt (h 80), dann 80 Punkte nach unten (v 80), dann 80 Punkte nach links (h -80) und schließlich zurück zum Start (z). Sie können dieses Beispiel auf der Seite Path2D constructor ansehen.
const p = new Path2D("M10 10 h 80 v 80 h -80 Z");