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 pittura

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 Lo spiegherò 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 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 pattern, 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 alla classe un attributo inputProperties statico, puoi iscriverti alle modifiche a 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 documento, solo Chrome ha implementato il worklet Paint. 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 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.

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 codice 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 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 di pittura, 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 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.

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: durante una riunione CSS, si è deciso che ora puoi avere più colori per il 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 rombo.
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. Con Chrome 65, è attivo per impostazione predefinita. Prova le nuove possibilità offerte dal worklet di Paint e mostraci cosa hai creato. Per ulteriori ispirazioni, consulta la raccolta di Vincent De Oliveira.