CSS Paint-API

Nieuwe mogelijkheden in Chrome 65

CSS Paint API (ook bekend als “CSS Custom Paint” of “Houdini's paint worklet”) is standaard ingeschakeld vanaf Chrome 65. Wat is het? Wat kun je ermee doen? En hoe werkt het? Nou, lees verder, wil je...

Met de CSS Paint API kunt u programmatisch een afbeelding genereren wanneer een CSS-eigenschap een afbeelding verwacht. Eigenschappen zoals background-image of border-image worden meestal gebruikt met url() om een ​​afbeeldingsbestand te laden of met ingebouwde CSS-functies zoals linear-gradient() . In plaats van deze te gebruiken, kunt u nu paint(myPainter) gebruiken om naar een verfwerklet te verwijzen.

Een verfwerkje schrijven

Om een ​​verfwerklet met de naam myPainter te definiëren, moeten we een CSS-verfwerkletbestand laden met behulp van CSS.paintWorklet.addModule('my-paint-worklet.js') . In dat bestand kunnen we de functie registerPaint gebruiken om een ​​verfwerkletklasse te registreren:

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

registerPaint('myPainter', MyPainter);

Binnen de callback paint() kunnen we ctx op dezelfde manier gebruiken als een CanvasRenderingContext2D zoals we die kennen van <canvas> . Als je weet hoe je in een <canvas> moet tekenen, kun je ook een verfwerkje tekenen! geometry vertelt ons de breedte en de hoogte van het canvas dat tot onze beschikking staat. properties zal ik later in dit artikel uitleggen.

Laten we als inleidend voorbeeld een schaakbordverfwerkje schrijven en dit gebruiken als achtergrondafbeelding van een <textarea> . (Ik gebruik een tekstgebied omdat dit standaard aanpasbaar is.):

<!-- 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);

Als je <canvas> in het verleden hebt gebruikt, zou deze code er bekend uit moeten zien. Bekijk hier de livedemo .

Tekstgebied met een schaakbordpatroon als achtergrondafbeelding
Tekstgebied met een schaakbordpatroon als achtergrondafbeelding.

Het verschil met het gebruik van een gewone achtergrondafbeelding is dat het patroon op verzoek opnieuw wordt getekend, telkens wanneer de gebruiker het tekstgebied vergroot of verkleint. Dit betekent dat de achtergrondafbeelding altijd precies zo groot is als nodig is, inclusief de compensatie voor weergaven met hoge dichtheid.

Dat is best gaaf, maar het is ook behoorlijk statisch. Zouden we elke keer een nieuw werkje willen schrijven als we hetzelfde patroon willen, maar met vierkanten van verschillende grootte? Het antwoord is nee!

Het parametriseren van uw werklet

Gelukkig heeft het verfwerklet toegang tot andere CSS-eigenschappen, en dat is waar de aanvullende properties een rol spelen. Door de klasse een statisch inputProperties attribuut te geven, kunt u zich abonneren op wijzigingen in elke CSS-eigenschap, inclusief aangepaste eigenschappen. De waarden worden aan u gegeven via de parameter properties .

<!-- 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);

Nu kunnen we dezelfde code gebruiken voor alle verschillende soorten damborden. Maar nog beter: we kunnen nu naar DevTools gaan en met de waarden spelen totdat we de juiste look vinden.

Browsers die geen Paint Worklet ondersteunen

Op het moment van schrijven heeft alleen Chrome een verfwerklet geïmplementeerd. Hoewel er positieve signalen zijn van alle andere browserleveranciers, is er niet veel vooruitgang. Om op de hoogte te blijven, ga naar Is Houdini Ready Yet? regelmatig. Zorg er in de tussentijd voor dat u progressieve verbeteringen gebruikt om uw code actief te houden, zelfs als er geen ondersteuning is voor verfwerkjes. Om ervoor te zorgen dat alles werkt zoals verwacht, moet je je code op twee plaatsen aanpassen: de CSS en de JS.

Het detecteren van ondersteuning voor paint worklet in JS kan worden gedaan door het CSS object te controleren: js if ('paintWorklet' in CSS) { CSS.paintWorklet.addModule('mystuff.js'); } Voor de CSS-kant heb je twee opties. U kunt @supports gebruiken:

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

Een compactere truc is om gebruik te maken van het feit dat CSS een volledige eigenschapsdeclaratie ongeldig maakt en vervolgens negeert als er een onbekende functie in zit. Als u een eigenschap twee keer opgeeft – eerst zonder verfwerkje en dan met het verfwerkje – krijgt u progressieve verbeteringen:

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

In browsers met ondersteuning voor paint worklet zal de tweede declaratie van de background-image de eerste overschrijven. In browsers zonder ondersteuning voor paint worklet is de tweede declaratie ongeldig en wordt deze verwijderd, waardoor de eerste declaratie van kracht blijft.

CSS Paint Polyfill

Voor veel toepassingen is het ook mogelijk om CSS Paint Polyfill te gebruiken, waarmee ondersteuning voor CSS Custom Paint en Paint Worklets wordt toegevoegd aan moderne browsers.

Gebruiksgevallen

Er zijn veel gebruiksscenario's voor verfwerkjes, waarvan sommige duidelijker zijn dan andere. Een van de meest voor de hand liggende is het gebruik van verfwerkjes om de grootte van uw DOM te verkleinen. Vaak worden elementen puur toegevoegd om versieringen te creëren met behulp van CSS. In Material Design Lite bevat de knop met het rimpeleffect bijvoorbeeld 2 extra <span> -elementen om de rimpel zelf te implementeren. Als je veel knoppen hebt, kan dit een behoorlijk aantal DOM-elementen opleveren en tot verminderde prestaties op mobiel leiden. Als je in plaats daarvan het rimpeleffect implementeert met een verfwerkje , krijg je uiteindelijk 0 extra elementen en slechts één verfwerkje. Bovendien heb je iets dat veel gemakkelijker is aan te passen en te parametriseren.

Een ander voordeel van het gebruik van Paint Worklet is dat - in de meeste scenario's - een oplossing die gebruik maakt van Paint Worklet klein is in termen van bytes. Natuurlijk is er een afweging: uw verfcode wordt uitgevoerd wanneer de grootte van het canvas of een van de parameters verandert. Dus als uw code complex is en lang duurt, kan dit jank introduceren. Chrome werkt eraan om verfwerkjes van de hoofddraad te verwijderen, zodat zelfs langlopende verfwerkjes de reactiesnelheid van de hoofddraad niet beïnvloeden.

Voor mij is het meest opwindende vooruitzicht dat Paint Worklet een efficiënte polyfilling van CSS-functies mogelijk maakt die een browser nog niet heeft. Een voorbeeld hiervan is het polyfillen van conische gradiënten totdat ze native in Chrome terechtkomen. Nog een voorbeeld: in een CSS-vergadering werd besloten dat je nu meerdere randkleuren kunt hebben. Terwijl deze bijeenkomst nog gaande was, schreef mijn collega Ian Kilpatrick een polyfill voor dit nieuwe CSS-gedrag met behulp van Paint Worklet.

Outside-the-box denken"

De meeste mensen beginnen na te denken over achtergrondafbeeldingen en randafbeeldingen wanneer ze leren over verfwerkjes. Een minder intuïtief gebruiksscenario voor verfwerkjes is mask-image om DOM-elementen willekeurige vormen te geven. Een diamant bijvoorbeeld:

Een DOM-element in de vorm van een diamant.
Een DOM-element in de vorm van een diamant.

mask-image neemt een afbeelding ter grootte van het element. Gebieden waar de maskerafbeelding transparant is, is het element transparant. Gebieden waar de maskerafbeelding ondoorzichtig is, is het element ondoorzichtig.

Nu in Chrome

Het verfwerkje staat al een tijdje in Chrome Canary. Bij Chrome 65 is dit standaard ingeschakeld. Ga je gang en probeer de nieuwe mogelijkheden die verfwerk biedt uit en laat ons zien wat je hebt gebouwd! Kijk voor meer inspiratie eens naar de collectie van Vincent De Oliveira .