API CSS Paint

Nuove possibilità in Chrome 65

L'API CSS Paint (nota anche come "CSS Custom Paint" o "Houdini's Paint worklet") è abilitata per impostazione predefinita a partire da Chrome 65. Di cosa si tratta? Cosa puoi farne? E come funziona? Continua a leggere...

L'API CSS Paint ti consente di generare un'immagine in modo programmatico ogni volta che una proprietà CSS desidera un'immagine. Proprietà come background-image o border-image di solito vengono utilizzate con url() per caricare un file immagine o con funzioni CSS integrate come linear-gradient(). Anziché utilizzare questi elementi, ora puoi usare paint(myPainter) per fare riferimento a un worklet di disegno.

Scrittura di un worklet

Per definire un worklet paint denominato myPainter, dobbiamo caricare un file paint worklet CSS utilizzando CSS.paintWorklet.addModule('my-paint-worklet.js'). In questo file, possiamo utilizzare la funzione registerPaint per registrare una classe di worklet di paint:

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

registerPaint('myPainter', MyPainter);

All'interno del callback paint(), possiamo utilizzare ctx come faresti con CanvasRenderingContext2D come lo conosciamo da <canvas>. Se sai come disegnare in <canvas>, puoi disegnare in un worklet di colorazione. geometry indica la larghezza e l'altezza della tela a nostra disposizione. properties Lo parlerò più avanti in questo articolo.

Come esempio introduttivo, scriviamo un worklet per colorare la scacchiera e usiamolo come immagine di sfondo di un elemento <textarea>. (Uso un'area di testo perché è ridimensionabile per impostazione predefinita).

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

Se hai utilizzato <canvas> in passato, questo codice dovrebbe esserti familiare. Guarda la demo dal vivo qui.

Area di testo con motivo a scacchi come immagine di sfondo
Area di testo con un motivo a scacchiera come immagine di sfondo.

La differenza rispetto all'uso di un'immagine di sfondo comune in questo caso è che il pattern viene ridisegnato su richiesta, ogni volta che l'utente ridimensiona l'area di testo. Ciò significa che l'immagine di sfondo è sempre esattamente grande quanto deve essere, inclusa la compensazione per i display ad alta densità.

È interessante, ma è anche piuttosto statico. Vogliamo scrivere un nuovo worklet ogni volta che volevamo lo stesso pattern ma con quadrati di dimensioni diverse? La risposta è no.

Parametrizzare il worklet

Fortunatamente, il worklet di Paint può accedere ad altre proprietà CSS, ed è qui che entra in gioco il parametro aggiuntivo properties. Se assegni alla classe un attributo inputProperties statico, puoi richiedere di apportare modifiche a qualsiasi proprietà CSS, incluse le proprietà personalizzate. I valori ti verranno assegnati tramite il parametro 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);

Ora possiamo utilizzare lo stesso codice per tutti i diversi tipi di scacchiera. Ma ancora meglio, ora possiamo entrare in DevTools e giocare con i valori fino a trovare l'aspetto giusto.

Browser che non supportano il worklet di paint

Al momento della scrittura, solo Chrome ha implementato il worklet di paint. Anche se ci sono indicatori positivi da parte di tutti gli altri fornitori di browser, non c'è molto progresso. Per ricevere aggiornamenti, seleziona regolarmente Houdini pronto?. Nel frattempo, assicurati di utilizzare il miglioramento progressivo per mantenere il codice in esecuzione anche se non è supportato il worklet di Paint. Per assicurarti che tutto funzioni come previsto, devi modificare il codice in due posizioni: nel CSS e JS.

Puoi rilevare il supporto del worklet di colorazione in JavaScript controllando l'oggetto CSS: js if ('paintWorklet' in CSS) { CSS.paintWorklet.addModule('mystuff.js'); } Per il lato CSS, hai due opzioni. Puoi utilizzare @supports:

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

Un trucco più compatto consiste nell'utilizzare il fatto che il CSS invalida e successivamente ignora l'intera dichiarazione di una proprietà se contiene una funzione sconosciuta. Se specifichi una proprietà due volte, prima senza worklet di paint e poi con il worklet di colorazione, ottieni il miglioramento progressivo:

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

Nei browser che supportano il worklet di colorazione, la seconda dichiarazione di background-image sovrascrive la prima. Nei browser senza supporto per il worklet paint, la seconda dichiarazione non è valida e verrà eliminata, lasciando in vigore la prima dichiarazione.

Polyfill della vernice CSS

Per molti utilizzi, è possibile utilizzare anche il CSS Paint Polyfill, che aggiunge il supporto dei Worklet CSS Custom Paint e Paint ai browser moderni.

Casi d'uso

Esistono molti casi d'uso per i worklet di colorazione, alcuni dei quali sono più evidenti di altri. Una delle più evidenti è l'utilizzo del worklet di colorazione per ridurre le dimensioni del DOM. Spesso gli elementi vengono aggiunti unicamente per creare abbellimenti utilizzando CSS. Ad esempio, in Material Design Lite il pulsante con l'effetto ondulazione contiene altri 2 elementi <span> per implementare l'onda stessa. Se sono presenti molti pulsanti, questa operazione può aggiungere fino a un certo numero di elementi DOM e portare a un peggioramento delle prestazioni sui dispositivi mobili. Se implementi l'effetto onde utilizzando il worklet di colorazione, il risultato è 0 elementi aggiuntivi e un solo worklet di colorazione. Inoltre, sono disponibili elementi molto più facili da personalizzare e parametrizzare.

Un altro vantaggio dell'utilizzo del worklet di Paint è che, nella maggior parte degli scenari, una soluzione che utilizza il worklet di color è piccola in termini di byte. Ovviamente esiste un compromesso: il codice di colorazione viene eseguito ogni volta che le dimensioni della tela o uno qualsiasi dei parametri cambiano. Quindi, se il tuo codice è complesso e richiede molto tempo, potrebbe introdurre Jank. Chrome sta cercando di spostare i worklet di Paint dal thread principale in modo che anche i worklet di Paint di lunga durata non influiscano sulla reattività del thread principale.

Per me, la prospettiva più entusiasmante è che il worklet di paint consente un efficiente polyfill delle funzionalità CSS che un browser non dispone ancora. Ad esempio, puoi eseguire il polyfill di gradienti conici finché non arrivano in Chrome in modo nativo. Un altro esempio: in una riunione CSS, si è deciso di avere più colori per i bordi. Mentre questo incontro era ancora in corso, il mio collega Ian Kilpatrick ha scritto un polyfill per questo nuovo comportamento CSS utilizzando il worklet di Paint.

Pensare fuori dagli schemi

Quando imparano a colorare il worklet, la maggior parte delle persone inizia a pensare alle immagini di sfondo e ai bordi. Un caso d'uso meno intuitivo per il worklet di colorazione è mask-image per rendere gli elementi DOM con forme arbitrarie. Ad esempio un diamante:

Un elemento DOM a forma di rombo.
Un elemento DOM a forma di rombo.

mask-image acquisisce un'immagine che ha le stesse dimensioni dell'elemento. Aree in cui l'immagine della maschera è trasparente e l'elemento è trasparente. Le aree in cui l'immagine della maschera è opaca, mentre l'elemento è opaco.

Ora in Chrome

Il worklet di colorazione è disponibile in Chrome Canary da un po' di tempo. In Chrome 65 è attivo per impostazione predefinita. Vai avanti e prova le nuove possibilità che si apre sul worklet di Paint e mostraci cosa hai creato. Per ulteriore ispirazione, dai un'occhiata alla collezione di Vincent De Oliveira.