CSS-Paint-API

Neue Möglichkeiten in Chrome 65

Die CSS Paint API, auch als „CSS Custom Paint“ oder „Houdini’s Paint Worklet“ bezeichnet, ist ab Chrome 65 standardmäßig aktiviert. Worum geht es? Was kann man damit machen? Und wie funktioniert das? Lies weiter, wir...

Mit der CSS Paint API können Sie jedes Mal 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. Statt diese zu verwenden, können Sie jetzt paint(myPainter) verwenden, um auf ein Paint Worklet zu verweisen.

Malerarbeiten

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

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

registerPaint('myPainter', MyPainter);

Innerhalb des paint()-Callbacks können wir ctx auf dieselbe Weise verwenden wie CanvasRenderingContext2D, wie wir es von <canvas> kennen. Wenn Sie wissen, wie Sie in einem <canvas> zeichnen, können Sie auch ein Paint-Worklet verwenden. geometry gibt die Breite und Höhe des Canvas an, die uns zur Verfügung stehen. properties Das erkläre ich später in diesem Artikel.

Als Einführungsbeispiel schreiben wir ein Schachbrett-Painting-Worklet und verwenden es als Hintergrundbild einer <textarea>. Ich verwende einen Textbereich, da seine 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> bereits verwendet haben, sollte Ihnen dieser Code bekannt vorkommen. Live-Demo ansehen

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 eine neue Workstation schreiben, wenn wir das gleiche Muster in Quadraten unterschiedlicher Größe haben möchten? Die Antwort lautet nein!

Worklet parametrieren

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 allen CSS-Eigenschaften abonnieren, einschließlich benutzerdefinierter Eigenschaften. Die Werte werden ü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 Arten von Schachbrettern verwenden. Noch besser ist es, dass wir jetzt in den Entwicklertools an den Werten experimentieren, bis wir das richtige Ergebnis gefunden haben.

Browser, die Paint Worklet nicht unterstützen

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

Um festzustellen, ob das Paint-Worklet in JS unterstützt wird, kannst du das CSS-Objekt prüfen: js if ('paintWorklet' in CSS) { CSS.paintWorklet.addModule('mystuff.js'); } Für die CSS-Seite hast du 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 Eigenschaftsdeklaration ungültig macht und anschließend ignoriert, wenn eine unbekannte Funktion enthalten ist. Wenn Sie eine Eigenschaft zweimal angeben – zuerst ohne Paint-Worklet und dann mit dem Mal-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-Worklets 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, sodass die erste Deklaration in Kraft tritt.

CSS-Farb-Polyfill

Für viele Anwendungsfälle ist es auch möglich, CSS Paint Polyfill zu verwenden, mit dem moderne Browser CSS Custom Paint und Paint Worklets unterstützen.

Anwendungsfälle

Es gibt viele Anwendungsfälle für Paint Worklets, einige davon offensichtlicher als andere. Eines der offensichtlichsten ist die Verwendung von Paint Worklet, um die Größe des 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, um die Welle selbst zu implementieren. Wenn Sie viele Schaltflächen haben, kann dies eine ganze Reihe von DOM-Elementen ergeben und die Leistung auf Mobilgeräten beeinträchtigen. Wenn Sie stattdessen den Welleneffekt mit einem Mal-Worklet implementieren, erhalten Sie 0 zusätzliche Elemente und nur ein Mal-Worklet. Außerdem haben Sie etwas, das sich viel einfacher anpassen und parametrisieren lässt.

Ein weiterer Vorteil der Verwendung von Paint Worklet ist, dass eine Lösung mit Paint Worklet in den meisten Szenarien in Bezug auf die Bytezahl kleiner ist. Natürlich müssen Sie Kompromisse eingehen: 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 es zu Verzögerungen kommen. Chrome arbeitet daran, Farb-Worklets aus dem Hauptthread zu verschieben, damit selbst langlebige Paint-Worklets die Reaktionsfähigkeit des Hauptthreads nicht beeinträchtigen.

Am interessantesten für mich ist, dass das Paint-Worklet eine effiziente Polyfilling-Funktion für 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 einem CSS-Meeting wurde beschlossen, jetzt mehrere Rahmenfarben verwenden zu können. Während dieses Treffens hat mein Kollege Ian Kilpatrick mit Paint Worklet ein Polyfill für dieses neue CSS-Verhalten geschrieben.

Über den Tellerrand blicken

Die meisten Menschen beginnen, über Hintergrundbilder und Rahmenbilder nachzudenken, wenn sie etwas über Paint Worklet lernen. Ein weniger intuitiver Anwendungsfall für das Paint-Worklet ist mask-image, mit dem DOM-Elemente beliebige Formen haben. Beispiel für einen diamond:

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

mask-image verwendet ein Bild in der Größe des Elements. Bereiche, in denen das Maskenbild transparent und das Element transparent ist. Bereiche, in denen das Maskenbild opak bzw. das Element undurchsichtig ist.

Jetzt in Chrome

Das 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 und zeigen Sie uns, was Sie gebaut haben! Sehen Sie sich zur Inspiration die Kollektion von Vincent De Oliveira an.