API CSS Paint

Nuove possibilità in Chrome 65

L'API CSS Paint (nota anche come "CSS Custom Paint" o "Worklet di pittura di Houdini") è attivata 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 Paint

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 in un <canvas>, puoi farlo anche in un worklet di pittura. geometry ci indica la larghezza e l'altezza della tela a nostra disposizione. properties Lo spiegherò più avanti in questo articolo.

Come esempio introduttivo, scriviamo un worklet di pittura a scacchi 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 scacchi 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 è sempre esattamente delle dimensioni necessarie, inclusa 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 pittura può accedere ad altre proprietà CSS, ed è 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 non perderti nessuna novità, consulta regolarmente la pagina 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.

Per rilevare il supporto del worklet di pittura in JS, puoi controllare l'oggetto CSS: js if ('paintWorklet' in CSS) { CSS.paintWorklet.addModule('mystuff.js'); } Per quanto riguarda il CSS, hai due opzioni. Puoi utilizzare @supports:

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

Un trucco più compatto consiste nell'utilizzare il fatto che il CSS convalida e successivamente ignora un'intera dichiarazione di proprietà se contiene 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 di pittura, 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 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 solo per creare abbellimenti utilizzando il 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, hai a disposizione un elemento molto più facile da personalizzare e parametroizzare.

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 per spostare i worklet di pittura dal thread principale in modo che anche quelli di lunga durata non influiscano sulla reattività del thread principale.

Per me, la prospettiva più entusiasmante è che il worklet di pittura consente un polyfilling efficiente delle funzionalità CSS che un browser non ha 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 pittura è mask-image dare agli elementi DOM 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

Il worklet di pittura è disponibile in Chrome Canary da un po' di tempo. In Chrome 65 è attivata per impostazione predefinita. Prova le nuove possibilità offerte dal worklet di Paint e mostraci cosa hai creato. Per ulteriori ispirazioni, consulta la collezione di Vincent De Oliveira.