CSS-Paint-API

Neue Möglichkeiten in Chrome 65

Die CSS Paint API (auch bekannt als „CSS Custom Paint“ oder „Houdini’s Paint Worklet“) wird ab Chrome 65 standardmäßig aktiviert. Worum geht es? Was können Sie damit tun? Und wie funktioniert das? Na ja, lies weiter, ja ...

Mit der CSS Paint API können Sie ein Bild programmatisch generieren, wenn eine CSS-Eigenschaft ein Bild erwartet. Attribute wie background-image oder border-image werden normalerweise mit url() zum Laden einer Bilddatei oder mit integrierten CSS-Funktionen wie linear-gradient() verwendet. Anstatt diese zu verwenden, können Sie jetzt mit paint(myPainter) auf ein Paint-Worklet verweisen.

Paint-Worklet schreiben

Um ein Paint-Worklet namens myPainter zu definieren, müssen wir eine CSS-Paint-Worklet-Datei mit CSS.paintWorklet.addModule('my-paint-worklet.js') laden. In dieser Datei können Sie mit der Funktion registerPaint eine Paint-Worklet-Klasse registrieren:

class MyPainter {
  paint(ctx, geometry, properties) {
    // ...
  }
}

registerPaint('myPainter', MyPainter);

Innerhalb des paint()-Callbacks können wir ctx genauso verwenden wie CanvasRenderingContext2D, wie wir es aus <canvas> kennen. Wenn Sie wissen, wie Sie in einem <canvas> zeichnen, können Sie auch ein Paint-Worklet nutzen. geometry gibt die Breite und Höhe des verfügbaren Canvas an. properties Ich erkläre es Ihnen später in diesem Artikel.

Schreiben wir als Einführungsbeispiel ein Paint-Worklet mit Schachbrettmustern und verwenden es als Hintergrundbild eines <textarea>. (Ich verwende einen Textbereich, da die Größe standardmäßig angepasst werden kann.):

<!-- index.html -->
<!doctype html>
<style>
  textarea {
    background-image: paint(checkerboard);
  }
</style>
<textarea></textarea>
<script>
  CSS.paintWorklet.addModule('checkerboard.js');
</script>
// checkerboard.js
class CheckerboardPainter {
  paint(ctx, geom, properties) {
    // Use `ctx` as if it was a normal canvas
    const colors = ['red', 'green', 'blue'];
    const size = 32;
    for(let y = 0; y < geom.height/size; y++) {
      for(let x = 0; x < geom.width/size; x++) {
        const color = colors[(x + y) % colors.length];
        ctx.beginPath();
        ctx.fillStyle = color;
        ctx.rect(x * size, y * size, size, size);
        ctx.fill();
      }
    }
  }
}

// Register our class under a specific name
registerPaint('checkerboard', CheckerboardPainter);

Wenn Sie <canvas> in der Vergangenheit verwendet haben, sollte Ihnen dieser Code bekannt vorkommen. Sehen Sie sich hier die Live-Demo an.

Textbereich mit Schachbrettmuster als Hintergrundbild
Textbereich mit einem Schachbrettmuster als Hintergrundbild.

Der Unterschied zu einem gängigen Hintergrundbild besteht darin, dass das Muster bei Bedarf neu gezeichnet wird, wenn der Nutzer die Größe des Textbereichs ändert. Das bedeutet, dass das Hintergrundbild immer genau so groß ist, wie es sein muss, einschließlich der Kompensation für hochauflösende Displays.

Das ist ziemlich cool, aber auch ziemlich statisch. Würden wir jedes Mal ein neues Worklet schreiben, wenn wir das gleiche Muster wollen, aber mit unterschiedlich großen Quadraten? Nein.

Worklet parametrisieren

Glücklicherweise kann das Paint-Worklet auf andere CSS-Eigenschaften zugreifen. Hier kommt der zusätzliche Parameter properties ins Spiel. Wenn Sie der Klasse ein statisches inputProperties-Attribut zuweisen, können Sie Änderungen an jedem CSS-Attribut abonnieren, einschließlich benutzerdefinierter Attribute. Die Werte werden Ihnen über den Parameter properties bereitgestellt.

<!-- index.html -->
<!doctype html>
<style>
  textarea {
    /* The paint worklet subscribes to changes of these custom properties. */
    --checkerboard-spacing: 10;
    --checkerboard-size: 32;
    background-image: paint(checkerboard);
  }
</style>
<textarea></textarea>
<script>
  CSS.paintWorklet.addModule('checkerboard.js');
</script>
// checkerboard.js
class CheckerboardPainter {
  // inputProperties returns a list of CSS properties that this paint function gets access to
  static get inputProperties() { return ['--checkerboard-spacing', '--checkerboard-size']; }

  paint(ctx, geom, properties) {
    // Paint worklet uses CSS Typed OM to model the input values.
    // As of now, they are mostly wrappers around strings,
    // but will be augmented to hold more accessible data over time.
    const size = parseInt(properties.get('--checkerboard-size').toString());
    const spacing = parseInt(properties.get('--checkerboard-spacing').toString());
    const colors = ['red', 'green', 'blue'];
    for(let y = 0; y < geom.height/size; y++) {
      for(let x = 0; x < geom.width/size; x++) {
        ctx.fillStyle = colors[(x + y) % colors.length];
        ctx.beginPath();
        ctx.rect(x*(size + spacing), y*(size + spacing), size, size);
        ctx.fill();
      }
    }
  }
}

registerPaint('checkerboard', CheckerboardPainter);

Jetzt können wir denselben Code für alle verschiedenen Arten von Schachbrettern verwenden. Aber noch besser: Wir können jetzt in den Entwicklertools mit den Werten herumspielen, bis wir den richtigen Look gefunden haben.

Browser, die Paint-Worklet nicht unterstützen

Zum Zeitpunkt der Entstehung dieses Artikels war das Paint-Worklet nur für Chrome implementiert. Es gibt zwar positive Signale von allen anderen Browseranbietern, aber es gibt keine großen Fortschritte. Sehen Sie regelmäßig unter Is Houdini Ready Yet? nach, um auf dem Laufenden zu bleiben. Verwenden Sie in der Zwischenzeit die progressive Verbesserung, damit Ihr Code auch dann ausgeführt wird, wenn kein Paint-Worklet unterstützt wird. Damit alles wie erwartet funktioniert, müssen Sie Ihren Code an zwei Stellen anpassen: im CSS und im JS.

Um festzustellen, ob Paint Worklet in JS unterstützt wird, können Sie das CSS-Objekt prüfen: js if ('paintWorklet' in CSS) { CSS.paintWorklet.addModule('mystuff.js'); } Auf der CSS-Seite haben Sie zwei Möglichkeiten. Sie können @supports verwenden:

@supports (background: paint(id)) {
  /* ... */
}

Ein kompakterer Trick besteht darin, die Tatsache zu nutzen, dass CSS eine ganze Attributdeklaration entwertet und anschließend ignoriert, wenn eine unbekannte Funktion darin enthalten ist. Wenn Sie eine Eigenschaft zweimal angeben – zuerst ohne Paint-Worklet und dann mit dem Paint-Worklet –, erhalten Sie Progressive Enhancement:

textarea {
  background-image: linear-gradient(0, red, blue);
  background-image: paint(myGradient, red, blue);
}

In Browsern mit Unterstützung für Paint-Worklet wird die erste Deklaration von background-image durch die zweite Deklaration überschrieben. In Browsern ohne Unterstützung für Paint-Worklet ist die zweite Deklaration ungültig und wird verworfen. Die erste Deklaration bleibt unverändert.

CSS-Paint-Polyfill

Für viele Anwendungsfälle können Sie auch CSS Paint Polyfill verwenden, das die Unterstützung von benutzerdefinierten CSS-Paint- und Paint-Worklets in modernen Browsern hinzufügt.

Anwendungsfälle

Es gibt viele Anwendungsfälle für Paint-Worklets, von denen einige offensichtlicher sind als andere. Eine der offensichtlichsten Methoden ist die Verwendung von Paint-Worklet, um die Größe Ihres DOMs zu reduzieren. Oft werden Elemente nur hinzugefügt, um mit CSS Verzierungen zu erstellen. In Material Design Lite enthält die Schaltfläche mit dem Welleneffekt beispielsweise zwei zusätzliche <span>-Elemente zur Implementierung der Welle selbst. Wenn Sie viele Schaltflächen haben, kann dies zu einer Vielzahl von DOM-Elementen führen und die Leistung auf Mobilgeräten beeinträchtigen. Wenn Sie den Ripple-Effekt stattdessen mit einem Paint-Worklet implementieren, erhalten Sie 0 zusätzliche Elemente und nur ein Paint-Worklet. Darüber hinaus haben Sie etwas, das viel einfacher angepasst und parametrisiert werden kann.

Ein weiterer Vorteil von Paint Worklet besteht darin, dass eine Lösung mit Paint-Worklet in den meisten Fällen in Bezug auf die Byte klein ist. Natürlich gibt es jedoch einen Kompromiss: Ihr Paint-Code wird immer dann ausgeführt, wenn sich die Größe des Canvas oder einer der Parameter ändert. Wenn Ihr Code also komplex ist und lange dauert, kann dies zu Verzögerungen führen. Chrome arbeitet daran, Paint-Worklets aus dem Hauptthread zu entfernen, damit selbst Paint-Worklets mit langer Ausführungszeit die Reaktionsfähigkeit des Hauptthreads nicht beeinträchtigen.

Für mich ist die interessanteste Perspektive, dass Paint-Worklet eine effiziente Polyfilling von CSS-Funktionen ermöglicht, die ein Browser noch nicht hat. Ein Beispiel wäre die Polyfill-Funktion für konische Farbverläufe, bis sie nativ in Chrome landen. Ein weiteres Beispiel: In einer CSS-Besprechung wurde beschlossen, dass jetzt mehrere Rahmenfarben möglich sind. Während das Meeting noch andauerte, hat mein Kollege Ian Kilpatrick mit Paint-Worklet einen Polyfill für dieses neue CSS-Verhalten geschrieben.

Über den Tellerrand blicken

Die meisten Nutzer denken beim ersten Mal über Hintergrundbilder und Rahmenbilder nach, wenn sie etwas über Paint-Worklet erfahren. Ein weniger intuitiver Anwendungsfall für Paint-Worklet ist mask-image, mit dem DOM-Elemente beliebige Formen haben. Beispiel eine Raute:

Ein DOM-Element in Form einer Raute.
Ein DOM-Element in Form einer Raute.

mask-image nimmt ein Bild auf, das die Größe des Elements hat. Bereiche, in denen das Maskenbild transparent und das Element transparent ist. Bereiche, in denen das Maskenbild „undurchsichtig“ ist, also das Element „undurchsichtig“.

Jetzt in Chrome

Paint Worklet ist schon seit einiger Zeit in Chrome Canary verfügbar. In Chrome 65 ist sie standardmäßig aktiviert. Probieren Sie die neuen Möglichkeiten aus, die sich durch ein Paint-Worklet ergeben, Weitere Anregungen finden Sie in der Kollektion von Vincent De Oliveira.