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 man damit machen? Und wie funktioniert das? Lesen Sie einfach weiter…

Mit der CSS Paint API können Sie programmatisch ein Bild generieren, wenn für eine CSS-Eigenschaft ein Bild erwartet wird. 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

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 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 ein CanvasRenderingContext2D, das wir aus <canvas> kennen. Wenn Sie wissen, wie man in einem <canvas> zeichnet, können Sie auch in einem Paint-Worklet zeichnen. geometry gibt uns die Breite und Höhe des verfügbaren Canvas an. properties Das werde ich später in diesem Artikel erläutern.

Als einführendes Beispiel schreiben wir ein Worklet für ein Schachbrettmuster und verwenden es als Hintergrundbild eines <textarea>. (Ich verwende ein Textfeld, da es standardmäßig in der Größe 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> schon einmal verwendet haben, sollte Ihnen dieser Code bekannt vorkommen. Live-Demo

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

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

Das ist zwar ganz nett, aber auch ziemlich statisch. Müssen wir jedes Mal ein neues Worklet schreiben, wenn wir dasselbe Muster, aber mit unterschiedlich großen Quadraten verwenden möchten? 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 einer beliebigen CSS-Eigenschaft abonnieren, einschließlich benutzerdefinierter Eigenschaften. Die Werte werden Ihnen über den Parameter properties zur Verfügung gestellt.

<!-- 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 Schachbrettmustern verwenden. Noch besser ist, dass wir jetzt in die DevTools gehen und mit den Werten herumspielen können, bis wir den richtigen Look gefunden haben.

Browser, die Paint Worklet nicht unterstützen

Zum Zeitpunkt der Erstellung dieses Dokuments ist das Paint Worklet nur in Chrome implementiert. Es gibt zwar positive Signale von allen anderen Browseranbietern, aber es gibt nicht viele Fortschritte. Auf der Seite Is Houdini Ready Yet? finden Sie aktuelle Informationen. Verwenden Sie in der Zwischenzeit Progressive Enhancement, damit Ihr Code auch dann ausgeführt wird, wenn keine Unterstützung für Paint Worklet vorhanden ist. Damit alles wie erwartet funktioniert, müssen Sie Ihren Code an zwei Stellen anpassen: im CSS und im JS.

Die Unterstützung für Paint Worklet in JS kann durch Überprüfen des CSS-Objekts erkannt werden: js if ('paintWorklet' in CSS) { CSS.paintWorklet.addModule('mystuff.js'); } Auf der CSS-Seite haben Sie zwei Möglichkeiten. Sie können @supports verwenden, um:

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

Ein kompakterer Trick besteht darin, dass CSS eine gesamte Eigenschaftendeklaration ungültig macht und ignoriert, wenn darin eine unbekannte Funktion 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 zweite Deklaration von background-image die erste überschreiben. In Browsern ohne Unterstützung für Paint Worklet ist die zweite Deklaration ungültig und wird verworfen. Die erste Deklaration bleibt in Kraft.

CSS Paint-Polyfill

Für viele Anwendungsfälle ist es auch möglich, das CSS Paint Polyfill zu verwenden, das modernen Browsern Unterstützung für CSS Custom 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 offensichtlicheren Möglichkeiten ist die Verwendung von Paint Worklet, um die Größe Ihres 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 zu implementieren. Wenn Sie viele Schaltflächen haben, kann das zu einer großen Anzahl von DOM-Elementen führen und die Leistung auf Mobilgeräten beeinträchtigen. Wenn Sie den Wellen-Effekt stattdessen mit einem Paint-Worklet implementieren, erhalten Sie 0 zusätzliche Elemente und nur ein Paint-Worklet. Außerdem haben Sie etwas, das sich viel einfacher anpassen und parametrisieren lässt.

Ein weiterer Vorteil der Verwendung von Paint Worklet besteht darin, dass eine Lösung mit Paint Worklet in den meisten Fällen nur wenige Bytes umfasst. Das hat natürlich auch Nachteile: Ihr Paint-Code wird immer dann ausgeführt, wenn sich die Größe des Canvas oder einer der Parameter ändert. Wenn Ihr Code komplex ist und lange dauert, kann es zu Rucklern kommen. Chrome arbeitet daran, Paint-Worklets aus dem Haupt-Thread zu entfernen, damit auch lang laufende Paint-Worklets die Reaktionsfähigkeit des Haupt-Threads nicht beeinträchtigen.

Am spannendsten finde ich, dass Paint Worklet ein effizientes Polyfilling von CSS-Funktionen ermöglicht, die ein Browser noch nicht unterstützt. Ein Beispiel wäre, konische Verläufe zu polyfillen, bis sie nativ in Chrome verfügbar sind. Ein weiteres Beispiel: Bei einem CSS-Treffen wurde beschlossen, dass Sie jetzt mehrere Rahmenfarben verwenden können. Während dieser Besprechung hat mein Kollege Ian Kilpatrick ein Polyfill für dieses neue CSS-Verhalten mit einem Paint-Worklet geschrieben.

Über den Tellerrand schauen

Die meisten denken an Hintergrundbilder und Rahmenbilder, wenn sie von Paint Worklet hören. Ein weniger intuitiver Anwendungsfall für Paint Worklet ist mask-image, um DOM-Elemente in beliebigen Formen darzustellen. Beispiel für einen Diamanten:

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

mask-image nimmt ein Bild an, das die Größe des Elements hat. In Bereichen, in denen das Maskenbild transparent ist, ist auch das Element transparent. In Bereichen, in denen das Maskenbild undurchsichtig ist, ist auch das Element undurchsichtig.

Jetzt in Chrome

Das Paint-Worklet ist schon seit einiger Zeit in Chrome Canary verfügbar. In Chrome 65 ist die Funktion standardmäßig aktiviert. Probieren Sie die neuen Möglichkeiten aus, die Paint Worklet bietet, und zeigen Sie uns, was Sie damit entwickelt haben. Hier findest du weitere Inspirationen.