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. Was ist das? Was kann ich damit machen? Und wie funktioniert das? Lesen Sie einfach weiter…

Mit der CSS Paint API können Sie ein Bild programmatisch generieren, wenn eine CSS-Eigenschaft ein Bild erwartet. Eigenschaften wie background-image oder border-image werden in der Regel mit url() verwendet, um eine Bilddatei zu laden, oder mit integrierten CSS-Funktionen wie linear-gradient(). Stattdessen können Sie jetzt paint(myPainter) verwenden, um auf ein Paint-Worklet zu verweisen.

Paint-Worklet schreiben

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

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

registerPaint('myPainter', MyPainter);

Im paint()-Callback können wir ctx genauso verwenden wie eine CanvasRenderingContext2D, wie wir sie von <canvas> kennen. Wenn Sie wissen, wie Sie in einem <canvas> zeichnen, können Sie auch in einem Paint-Worklet zeichnen. geometry gibt die Breite und Höhe des Canvas an, der uns zur Verfügung steht. properties Wie das geht, erkläre ich später in diesem Artikel.

Als Einführungsbeispiel schreiben wir ein Worklet zum Malen eines Schachbrettmusters und verwenden es als Hintergrundbild einer <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> bereits verwendet haben, sollte Ihnen dieser Code bekannt vorkommen. Hier können Sie sich die Live-Demo ansehen.

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

Der Unterschied zur Verwendung eines normalen Hintergrundbilds besteht darin, dass das Muster auf Anfrage neu gezeichnet wird, wenn der Nutzer die Größe des Textfelds ändert. Das bedeutet, dass das Hintergrundbild immer genau so groß ist, wie es sein muss, einschließlich der Kompensation für Displays mit hoher Pixeldichte.

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? Die Antwort lautet: 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 jeder CSS-Eigenschaft abonnieren, einschließlich benutzerdefinierter Eigenschaften. 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 Arten von Schachbrettern verwenden. Noch besser ist, dass wir jetzt in den DevTools mit den Werten herumspielen können, bis wir den gewünschten Look gefunden haben.

Browser, die Paint-Worklet nicht unterstützen

Derzeit ist das Paint-Worklet nur in Chrome implementiert. Es gibt zwar positive Signale von allen anderen Browseranbietern, aber es gibt keine großen Fortschritte. Um auf dem Laufenden zu bleiben, sieh dir regelmäßig Is Houdini Ready Yet? an. In der Zwischenzeit sollten Sie die progressive Verbesserung verwenden, damit Ihr Code auch dann ausgeführt wird, wenn Paint Worklet nicht unterstützt wird. Damit alles wie erwartet funktioniert, müssen Sie Ihren Code an zwei Stellen anpassen: im CSS und im JS.

Sie können die Unterstützung für das Paint-Worklet in JS prüfen, indem Sie das CSS-Objekt überprü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 gesamte Eigenschaftsdeklaration ungültig macht und anschließend ignoriert, wenn sie eine unbekannte Funktion enthält. Wenn Sie eine Property zweimal angeben – zuerst ohne Paint-Worklet und dann mit dem Paint-Worklet –, erhalten Sie eine progressive Verbesserung:

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

In Browsern, die Paint Worklets unterstützen, wird die erste Deklaration von background-image durch die zweite überschrieben. In Browsern, die keine Unterstützung für Paint-Worklets bieten, ist die zweite Deklaration ungültig und wird verworfen. Die erste Deklaration bleibt in Kraft.

CSS Paint Polyfill

Für viele Anwendungen ist auch der CSS Paint Polyfill verfügbar, der modernen Browsern Unterstützung für benutzerdefiniertes CSS-Paint und Paint-Worklets hinzufügt.

Anwendungsfälle

Es gibt viele Anwendungsfälle für Paint-Worklets, einige davon sind offensichtlicher als andere. Eine der offensichtlichsten Möglichkeiten ist die Verwendung eines Paint-Worklets, um die Größe des DOM 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 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 des Paint-Worklets ist, dass eine Lösung mit Paint-Worklet in den meisten Fällen nur wenige Byte groß ist. Natürlich gibt es einen Trade-off: Ihr Paint-Code wird immer 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 Rucklern kommen. In Chrome wird daran gearbeitet, Paint-Worklets aus dem Haupt-Thread zu entfernen, damit auch langlaufende Paint-Worklets die Reaktionsfähigkeit des Haupt-Threads nicht beeinträchtigen.

Für mich ist das Beste daran, dass Paint Worklets ein effizientes Polyfilling von CSS-Funktionen ermöglichen, die in einem Browser noch nicht vorhanden sind. Ein Beispiel wäre die Polyfill-Funktion für konische Farbverläufe, bis sie nativ in Chrome unterstützt werden. Ein weiteres Beispiel: In einer CSS-Sitzung wurde beschlossen, dass es jetzt mehrere Rahmenfarben geben kann. Während das Meeting noch andauerte, hat mein Kollege Ian Kilpatrick mit Paint-Worklet einen Polyfill für dieses neue CSS-Verhalten geschrieben.

Unkonventionell denken

Die meisten Menschen denken an Hintergrund- und Rahmenbilder, wenn sie das Paint-Worklet kennenlernen. Ein weniger intuitiver Anwendungsfall für das Paint-Worklet ist die mask-imageZuweisung beliebiger Formen an DOM-Elemente. Beispiel: Raute:

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

mask-image verwendet ein Bild, das der Größe des Elements entspricht. In Bereichen, in denen das Maskierungsbild transparent ist, ist auch das Element transparent. Bereiche, in denen das Maskierungsbild opak ist, ist das Element opak.

Jetzt in Chrome

Das Paint-Worklet ist schon seit einiger Zeit in Chrome Canary verfügbar. In Chrome 65 ist es standardmäßig aktiviert. Probieren Sie die neuen Möglichkeiten aus, die das Paint-Worklet bietet, und zeigen Sie uns, was Sie damit erstellt haben. Weitere Inspirationen finden Sie in der Sammlung von Vincent De Oliveira.