Das waren schon immer Sie, Canvas2D.

Aaron Krajeski
Aaron Krajeski

In einer Welt voller Shader, Meshes und Filter ist Canvas2D vielleicht nicht so aufregend. Das sollte es aber. 30–40% der Webseiten haben ein <canvas>-Element und 98% aller Canvas-Elemente verwenden einen Canvas2D-Renderingkontext. Canvas2D-Elemente gibt es in Autos, auf Kühlschränken und im Weltraum (wirklich).

Die API ist in Bezug auf moderne 2D-Zeichnungen etwas veraltet. Wir haben jedoch hart daran gearbeitet, neue Funktionen in Canvas2D zu implementieren, um mit CSS gleichzuziehen, die Ergonomie zu optimieren und die Leistung zu verbessern.

Teil 1: CSS-Grundlagen

CSS hat einige Zeichenbefehle, die in Canvas2D schmerzlich fehlen. Mit der neuen API haben wir einige der am häufigsten gewünschten Funktionen hinzugefügt:

Abgerundetes Rechteck

Abgerundete Rechtecke: der Eckpfeiler des Internets, des Computings, ja, der Zivilisation.

Ganz im Ernst: Abgerundete Rechtecke sind äußerst nützlich – als Schaltflächen, Chatblasen, Thumbnails, Sprechblasen usw. Es war schon immer möglich, in Canvas2D ein abgerundetes Rechteck zu erstellen, aber es war etwas umständlich:

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

const top = 10;
const left = 10;
const width = 200;
const height = 100;
const radius = 20;

ctx.beginPath();
ctx.moveTo(left + radius, top);
ctx.lineTo(left + width - radius, top);
ctx.arcTo(left + width, top, left + width, top + radius, radius);
ctx.lineTo(left + width, top + height - radius);
ctx.arcTo(left + width, top + height, left + width - radius, top + height, radius);
ctx.lineTo(left + radius, top + height);
ctx.arcTo(left, top + height, left, top + height - radius, radius);
ctx.lineTo(left, top + radius);
ctx.arcTo(left, top, left + radius, top, radius);
ctx.stroke();

All das war für ein bescheidenes, einfaches abgerundetes Rechteck erforderlich:

Ein abgerundetes Rechteck.

Die neue API bietet die Methode roundRect().

ctx.roundRect(upper, left, width, height, borderRadius);

Der obige Code kann also vollständig durch Folgendes ersetzt werden:

ctx.roundRect(10, 10, 200, 100, 20);

Die Methode ctx.roundRect() akzeptiert auch ein Array für das borderRadius-Argument mit bis zu vier Zahlen. Diese Radien steuern die vier Ecken des abgerundeten Rechtecks auf dieselbe Weise wie bei CSS. Beispiel:

ctx.roundRect(10, 10, 200, 100, [15, 50, 30]);

Demo ansehen

Kegelförmiger Verlauf

Lineare Farbverläufe kennen Sie sicher:

const gradient = ctx.createLinearGradient(0, 0, 200, 100);
gradient.addColorStop(0, 'blue');
gradient.addColorStop(0.5, 'magenta');
gradient.addColorStop(1, 'white');
ctx.fillStyle = gradient;
ctx.fillRect(10, 10, 200, 100);

Ein linearer Farbverlauf.

Radiale Farbverläufe:

const radialGradient = ctx.createRadialGradient(150, 75, 10, 150, 75, 70);
radialGradient.addColorStop(0, 'white');
radialGradient.addColorStop(0.5, 'magenta');
radialGradient.addColorStop(1, 'lightblue');

ctx.fillStyle = radialGradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);

Ein radialer Farbverlauf.

Aber wie wäre es mit einem schönen kegelförmigen Farbverlauf?

const grad = ctx.createConicGradient(0, 100, 100);

grad.addColorStop(0, 'red');
grad.addColorStop(0.25, 'orange');
grad.addColorStop(0.5, 'yellow');
grad.addColorStop(0.75, 'green');
grad.addColorStop(1, 'blue');

ctx.fillStyle = grad;
ctx.fillRect(0, 0, 200, 200);

Ein konischer Farbverlauf.

Textmodifikatoren

Die Textrendering-Funktionen von Canvas2D waren bisher unzureichend. In Chrome wurden dem Canvas2D-Textrendering mehrere neue Attribute hinzugefügt:

Diese Attribute entsprechen den Attributen mit denselben Namen in Preisvergleichsportalen.

Teil 2: Ergonomische Anpassungen

Bisher waren einige Dinge mit Canvas2D möglich, aber unnötig kompliziert zu implementieren. Hier sind einige Verbesserungen für JavaScript-Entwickler, die Canvas2D verwenden möchten:

Kontext zurücksetzen

Um das Löschen eines Canvas zu veranschaulichen, habe ich eine einfache Funktion zum Zeichnen eines Retromusters geschrieben:

draw90sPattern();

Ein Retro-Muster aus Dreiecken und Quadraten.

Sehr gut! Jetzt, da ich mit diesem Muster fertig bin, möchte ich die Arbeitsfläche leeren und etwas anderes zeichnen. Moment, wie leeren wir eine Arbeitsfläche noch einmal? Oh ja! ctx.clearRect(), natürlich.

ctx.clearRect(0, 0, canvas.width, canvas.height);

Das hat nicht funktioniert. Oh ja! Ich muss die Transformation zuerst zurücksetzen:

ctx.resetTransform();
ctx.clearRect(0, 0, canvas.width, canvas.height);
Eine leere Leinwand.

Super! Eine schöne leere Leinwand. Zeichnen wir nun eine schöne horizontale Linie:

ctx.moveTo(10, 10);
ctx.lineTo(canvas.width, 10);
ctx.stroke();

Eine horizontale und eine diagonale Linie.

Grrrr! Das stimmt nicht! 😡 Was macht diese zusätzliche Zeile hier? Und warum ist es rosa? Okay, schauen wir mal auf Stack Overflow nach.

canvas.width = canvas.width;

Warum ist das so albern? Warum ist das so schwer?

Das ist nicht mehr der Fall. Die neue API bietet folgende Vorteile:

ctx.reset();

Entschuldige, dass es so lange gedauert hat.

Filter

SVG-Filter sind eine Welt für sich. Wenn Sie noch nicht mit SVG-Filtern vertraut sind, empfehle ich Ihnen, The Art Of SVG Filters And Why It Is Awesome zu lesen, in dem einige ihrer erstaunlichen Möglichkeiten gezeigt werden.

SVG-Stilfilter sind bereits für Canvas2D verfügbar. Sie müssen nur bereit sein, den Filter als URL zu übergeben, die auf ein anderes SVG-Filterelement auf der Seite verweist:

<svg>
  <defs>
    <filter id="svgFilter">
      <feGaussianBlur in="SourceGraphic" stdDeviation="5" />
      <feConvolveMatrix kernelMatrix="-3 0 0 0 0.5 0 0 0 3" />
      <feColorMatrix type="hueRotate" values="90" />
    </filter>
  </defs>
</svg>
const canvas = document.createElement('canvas');
canvas.width = 500;
canvas.height = 400;
const ctx = canvas.getContext('2d');
document.body.appendChild(canvas);

ctx.filter = "url('#svgFilter')";
draw90sPattern(ctx);

Das stört unser Muster ziemlich:

Das Retro-Muster mit einem Weichzeichnungseffekt.

Was aber, wenn Sie das oben Genannte tun möchten, aber in JavaScript bleiben und nicht mit Strings arbeiten möchten? Mit der neuen API ist das problemlos möglich.

ctx.filter = new CanvasFilter([
  { filter: 'gaussianBlur', stdDeviation: 5 },
  {
    filter: 'convolveMatrix',
    kernelMatrix: [
      [-3, 0, 0],
      [0, 0.5, 0],
      [0, 0, 3],
    ],
  },
  { filter: 'colorMatrix', type: 'hueRotate', values: 90 },
]);

Ganz einfach. Hier können Sie die Demo ausprobieren und mit den Parametern spielen.

Teil 3: Leistungsverbesserungen

Mit der neuen Canvas2D API wollten wir auch die Leistung verbessern, wo immer es möglich ist. Wir haben einige Funktionen hinzugefügt, um Entwicklern eine detailliertere Kontrolle über ihre Websites zu ermöglichen und die bestmöglichen Framerates zu erzielen:

Häufig lesen

Verwenden Sie getImageData(), um Pixeldaten aus einem Canvas zu lesen. Das kann sehr lange dauern. Mit der neuen API können Sie einen Canvas explizit für das Auslesen markieren (z. B. für generative Effekte). So können Sie die Leistung optimieren und das Canvas für eine Vielzahl von Anwendungsfällen schnell halten. Diese Funktion ist schon seit einiger Zeit in Firefox verfügbar und wird nun endlich Teil der Canvas-Spezifikation.

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });

Kontextverlust

Wir machen traurige Tabs wieder glücklich! Wenn einem Client der GPU-Arbeitsspeicher ausgeht oder ein anderes Problem mit Ihrem Canvas auftritt, können Sie jetzt einen Callback erhalten und das Canvas nach Bedarf neu zeichnen:

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

canvas.addEventListener('contextlost', onContextLost);
canvas.addEventListener('contextrestored', redraw);

Weitere Informationen zu Canvas-Kontext und -Verlust finden Sie im WHATWG-Wiki.

Fazit

Egal, ob Sie Canvas2D noch nie verwendet haben, es seit Jahren nutzen oder es seit Jahren vermeiden – ich möchte Ihnen ans Herz legen, sich Canvas noch einmal anzusehen. Es ist die API von nebenan, die schon immer da war.