API CSS Paint

Nuove possibilità in Chrome 65

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

L'API CSS Paint ti consente di generare in modo programmatico un'immagine ogni volta che una proprietà CSS prevede un'immagine. In genere, proprietà come background-image o border-image vengono utilizzate con url() per caricare un file immagine o con funzioni incorporate di CSS come linear-gradient(). Invece di utilizzarli, ora puoi usare paint(myPainter) per fare riferimento a un worklet di colorazione.

Scrivere un worklet di colorazione

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

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

registerPaint('myPainter', MyPainter);

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

Come esempio introduttivo, scriviamo un worklet di pittura a scacchiera e utilizziamolo come immagine di sfondo di un <textarea>. (sto usando 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, il codice dovrebbe esserti familiare. Guarda la demo dal vivo qui.

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

La differenza rispetto all'utilizzo di un'immagine di sfondo comune in questo caso è che il motivo verrà ridisegnato su richiesta, ogni volta che l'utente ridimensiona l'area di testo. Ciò significa che l'immagine di sfondo ha sempre le dimensioni necessarie, compresa la compensazione per i display ad alta densità.

Non male, ma è anche statico. Dovremmo scrivere un nuovo worklet ogni volta che vogliamo lo stesso pattern, ma con quadrati di dimensioni diverse? La risposta è no.

Parametrizzazione del worklet

Fortunatamente, il worklet di colorazione può accedere ad altre proprietà CSS, qui che entra in gioco il parametro aggiuntivo properties. Se assegni alla classe un attributo inputProperties statico, puoi iscriverti alle 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 soprattutto, ora possiamo usare DevTools e giocare ai valori fino a trovare il look giusto.

Browser che non supportano il worklet di colorazione

Al momento della stesura di questo documento, solo Chrome ha implementato il worklet Paint. Sebbene esistano segnali positivi da parte di tutti gli altri fornitori di browser, non ci sono molti progressi. Per tenerti al corrente, controlla regolarmente la sezione Is Houdini Ready Yet?. Nel frattempo, assicurati di utilizzare il miglioramento progressivo per mantenere il codice in esecuzione anche se non è disponibile il supporto per il worker Paint. Per assicurarti che tutto funzioni come previsto, devi modificare il codice in due punti: il CSS e il codice JS.

Il rilevamento del supporto del worklet di colorazione in JS può essere eseguito 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 è utilizzare il fatto che CSS invalida e successivamente ignora un'intera dichiarazione di proprietà se è presente una funzione sconosciuta. Se specifichi una proprietà due volte, prima senza il worklet di colorazione e poi con il worklet di colorazione, ottieni un miglioramento progressivo:

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

Nei browser con supporto per il worklet Paint, la seconda dichiarazione di background-image sovrascriverà la prima. Nei browser senza supporto per il worklet di colorazione, la seconda dichiarazione non è valida e verrà ignorata, lasciando attiva la prima dichiarazione.

Pittura CSS Polyfill

Per molti utilizzi, puoi anche utilizzare CSS Paint Polyfill, che aggiunge ai browser moderni il supporto CSS Custom Paint e Paint.

Casi d'uso

Esistono molti casi d'uso per i worklet di pittura, alcuni dei quali più evidenti di altri. Uno dei più evidenti è l'utilizzo di un worklet di colorazione per ridurre le dimensioni del DOM. Spesso, gli elementi vengono aggiunti esclusivamente per creare abbellimenti utilizzando CSS. Ad esempio, in Material Design Lite il pulsante con l'effetto Onde contiene due elementi <span> aggiuntivi per implementare l'eco stesso. La presenza di molti pulsanti può causare la somma di numerosi elementi DOM e un peggioramento delle prestazioni sui dispositivi mobili. Se invece implementi l'effetto a onde utilizzando il worklet di colorazione, ottieni 0 elementi aggiuntivi e un solo worklet di colorazione. Inoltre, c'è qualcosa che è molto più facile da personalizzare e parametrizzare.

Un altro vantaggio dell'utilizzo del worklet di colorazione è che, nella maggior parte degli scenari, una soluzione che utilizza il worklet di colorazione è piccola in termini di byte. Naturalmente, c'è un compromesso: il codice viene eseguito ogni volta che le dimensioni della tela o dei parametri cambiano. Pertanto, se il tuo codice è complesso e richiede molto tempo, potrebbe introdurre jank. Chrome sta lavorando allo spostamento dei worklet di colorazione dal thread principale in modo che anche quelli di colorazione a lunga esecuzione non influiscano sulla reattività del thread principale.

Secondo me, la prospettiva più entusiasmante è che il worklet di colorazione consente un polyfill efficiente delle funzionalità CSS di cui un browser non dispone ancora. Un esempio potrebbe essere il polyfill dei gradienti conici finché non arrivano in Chrome in modo nativo. Un altro esempio: durante una riunione CSS, si è deciso che ora puoi avere più colori per il bordo. Mentre l'incontro era ancora in corso, il mio collega Ian Kilpatrick ha scritto un polyfill per questo nuovo comportamento CSS utilizzando un worklet di colorazione.

Pensare fuori dagli schemi

La maggior parte delle persone inizia a pensare alle immagini di sfondo e alle immagini dei bordi quando impara a vedere il worklet di colorazione. Un caso d'uso meno intuitivo per il worklet di colorazione è mask-image, che consente di fare in modo che gli elementi DOM abbiano 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 corrisponda alle dimensioni dell'elemento. Aree in cui l'immagine della maschera è trasparente, l'elemento è trasparente. Aree in cui l'immagine della maschera è opaca, l'elemento è opaco.

Ora in Chrome

Colorazione del worklet è stata utilizzata per un po' di tempo in Chrome Canary. Con Chrome 65, è attivo per impostazione predefinita. Prova le nuove possibilità che si aprono sul worklet di pittura e mostraci cosa hai creato. Per ulteriori spunti, dai un'occhiata alla collezione di Vincent De Oliveira.