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. Che cos'è? Cosa puoi fare con questo strumento? E come funziona? Continua a leggere...

L'API CSS Paint consente di generare un'immagine tramite programmazione ogni volta che una proprietà CSS si aspetta un'immagine. Proprietà come background-image o border-image vengono in genere utilizzate con url() per caricare un file immagine o con le funzioni predefinite del CSS come linear-gradient(). Ora, invece di utilizzare questi valori, puoi usare paint(myPainter) per fare riferimento a un worklet di pittura.

Scrivere un worklet di colorazione

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

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

registerPaint('myPainter', MyPainter);

All'interno del callback paint(), possiamo utilizzare ctx nello stesso modo in cui useremmo un CanvasRenderingContext2D come lo conosciamo da <canvas>. Se sai come disegnare con un <canvas>, puoi disegnare un worklet! geometry ci 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 lo utilizziamo come immagine di sfondo di un <textarea>. (Utilizzo 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 già utilizzato <canvas> in passato, questo 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 scacchi come immagine di sfondo.

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

È fantastico, ma è anche piuttosto statico. Dovremmo scrivere un nuovo worklet ogni volta che vogliamo lo stesso motivo, ma con quadrati di dimensioni diverse? La risposta è no.

Parametrizzare il worklet

Fortunatamente, il worklet di colorazione può accedere ad altre proprietà CSS, qui che entra in gioco il parametro aggiuntivo properties. Se assegni al corso un attributo statico inputProperties, puoi iscriverti alle modifiche di qualsiasi proprietà CSS, incluse le proprietà personalizzate. I valori ti verranno forniti tramite il parametroproperties.

<!-- 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 c'è di più: ora possiamo andare in DevTools e modificare i valori fino a trovare l'aspetto giusto.

Browser che non supportano il worklet di pittura

Al momento della stesura di questo articolo, solo Chrome ha implementato il worklet di pittura. Anche se esistono indicatori positivi da parte di tutti gli altri fornitori di browser, non si sono registrati molti progressi. Per tenerti al corrente, controlla regolarmente la sezione Is Houdini Ready Yet?. Nel frattempo, assicurati di utilizzare il miglioramento progressivo per mantenere in esecuzione il codice anche se non è supportato il worklet di pittura. Per assicurarti che tutto funzioni come previsto, devi modificare il codice in due punti: CSS e 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 pittura e poi con il worklet di pittura, ottieni il 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 pittura, la seconda dichiarazione non è valida e verrà ignorata, lasciando in vigore la prima dichiarazione.

CSS Paint Polyfill

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

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 del worklet di pittura 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 ripple contiene altri due elementi <span> per implementare l'effetto stesso. Se hai molti pulsanti, il numero di elementi DOM può aumentare notevolmente e le prestazioni su dispositivi mobili possono peggiorare. Se invece implementi l'effetto ripple utilizzando il worklet di pittura, ottieni 0 elementi aggiuntivi e un solo worklet di pittura. Inoltre, è disponibile qualcosa che è molto più facile da personalizzare e parametrizzare.

Un altro vantaggio dell'utilizzo del worklet di pittura è che, nella maggior parte dei casi, una soluzione che lo utilizza è di piccole dimensioni in termini di byte. Ovviamente, c'è un compromesso: il codice di pittura verrà eseguito ogni volta che le dimensioni della tela o uno dei parametri cambiano. Pertanto, se il codice è complesso e richiede molto tempo, potrebbe introdurre ritardi. 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 implementare il polyfill per le sfumature coniche finché non vengono implementate in modo nativo in Chrome. Un altro esempio: in una riunione CSS è stato deciso che ora puoi avere più colori di bordo. Mentre la riunione era ancora in corso, il mio collega Ian Kilpatrick ha scritto un polyfill per questo nuovo comportamento CSS utilizzando il worklet di pittura.

Pensare fuori dagli schemi

La maggior parte delle persone inizia a pensare alle immagini di sfondo e ai bordi quando scopre il worklet di pittura. 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 diamante.
Un elemento DOM a forma di diamante.

mask-image acquisisce un'immagine delle dimensioni dell'elemento. Le aree in cui l'immagine della maschera è trasparente, l'elemento è trasparente. Aree in cui l'immagine della maschera è opaca, l'elemento opaco.

Ora su Chrome

Colorazione del worklet in Chrome Canary da un po' di tempo. In Chrome 65 è attivata per impostazione predefinita. Prova le nuove possibilità che si aprono sul worklet di pittura e mostraci cosa hai creato. Per ulteriori ispirazioni, consulta la raccolta di Vincent De Oliveira.