API CSS Paint

Nouvelles possibilités dans Chrome 65

L'API CSS Paint (également appelée "CSS Custom Paint" ou "Worklet de peinture de Houdini") est activée par défaut à partir de Chrome 65. De quoi s'agit-il ? À quoi sert-il ? Et comment ça marche ? Eh bien, lisez la suite :

L'API CSS Paint vous permet de générer une image de manière programmatique chaque fois qu'une propriété CSS attend une image. Les propriétés telles que background-image ou border-image sont généralement utilisées avec url() pour charger un fichier image ou avec des fonctions intégrées CSS telles que linear-gradient(). Au lieu de les utiliser, vous pouvez désormais utiliser paint(myPainter) pour référencer un worklet de peinture.

Écrire un Worklet de peinture

Pour définir un worklet de peinture appelé myPainter, nous devons charger un fichier de worklet de peinture CSS à l'aide de CSS.paintWorklet.addModule('my-paint-worklet.js'). Dans ce fichier, nous pouvons utiliser la fonction registerPaint pour enregistrer une classe de worklet de peinture :

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

registerPaint('myPainter', MyPainter);

Dans le rappel paint(), nous pouvons utiliser ctx de la même manière que nous le ferions avec un CanvasRenderingContext2D tel que nous le connaissons de <canvas>. Si vous savez dessiner dans un <canvas>, vous pouvez dessiner dans un worklet de peinture. geometry indique la largeur et la hauteur du canevas à notre disposition. properties Je vous expliquerai plus tard dans cet article.

Pour commencer, écrivons un worklet de peinture en damier et utilisons-le comme image de fond d'un <textarea>. (J'utilise un textarea, car il est redimensionnable par défaut.) :

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

Si vous avez déjà utilisé <canvas>, ce code devrait vous sembler familier. Pour voir la démonstration en direct, cliquez ici.

Zone de texte avec un motif en damier comme image de fond
Zone de texte avec un motif en damier comme image de fond.

La différence avec l'utilisation d'une image de fond commune est que le motif sera redessiné à la demande, chaque fois que l'utilisateur redimensionnera le champ de texte. Cela signifie que l'image de fond est toujours aussi grande qu'elle doit l'être, y compris la compensation pour les écrans haute densité.

C'est plutôt cool, mais c'est aussi assez statique. Voulez-vous écrire un nouveau worklet chaque fois que vous souhaitez le même motif, mais avec des carrés de taille différente ? La réponse est non !

Paramétrer votre worklet

Heureusement, le worklet de peinture peut accéder à d'autres propriétés CSS, c'est là que le paramètre supplémentaire properties entre en jeu. En attribuant à la classe un attribut inputProperties statique, vous pouvez vous abonner aux modifications apportées à n'importe quelle propriété CSS, y compris les propriétés personnalisées. Les valeurs vous seront fournies via le paramètre 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);

Nous pouvons maintenant utiliser le même code pour tous les types de damiers. Mieux encore, nous pouvons accéder aux outils de développement et manipuler les valeurs jusqu'à ce que nous trouvions l'apparence appropriée.

Navigateurs non compatibles avec le worklet de peinture

Au moment de la rédaction de cet article, seul Chrome a implémenté le worklet de peinture. Bien que des signaux positifs soient émis par tous les autres fournisseurs de navigateurs, les progrès sont peu nombreux. Pour vous tenir informé, consultez régulièrement Est-ce que Houdini est prêt ? En attendant, veillez à utiliser l'amélioration progressive pour que votre code continue de s'exécuter même si le travaillet de peinture n'est pas pris en charge. Pour vous assurer que tout fonctionne comme prévu, vous devez ajuster votre code à deux endroits: le CSS et le JS.

Pour détecter la compatibilité avec le Worklet de peinture en JavaScript, vous pouvez vérifier l'objet CSS : js if ('paintWorklet' in CSS) { CSS.paintWorklet.addModule('mystuff.js'); } Côté CSS, deux options s'offrent à vous. Vous pouvez utiliser @supports:

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

Une astuce plus compacte consiste à utiliser le fait que le CSS invalide et ignore ensuite l'ensemble d'une déclaration de propriété s'il contient une fonction inconnue. Si vous spécifiez une propriété deux fois (d'abord sans le worklet de peinture, puis avec le worklet de peinture), vous obtenez une amélioration progressive:

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

Dans les navigateurs compatibles avec le Worklet de peinture, la deuxième déclaration de background-image écrase la première. Dans les navigateurs non compatibles avec le Worklet Paint, la deuxième déclaration n'est pas valide et sera supprimée, ce qui laisse la première déclaration en vigueur.

Polyfill de peinture CSS

Pour de nombreuses utilisations, vous pouvez également utiliser le polyfill CSS Paint, qui ajoute la compatibilité avec CSS Custom Paint et Paint Worklets aux navigateurs modernes.

Cas d'utilisation

Il existe de nombreux cas d'utilisation des worklets de peinture, certains plus évidents que d'autres. L'une des plus évidentes consiste à utiliser un worklet de peinture pour réduire la taille de votre DOM. Souvent, des éléments sont ajoutés uniquement pour créer des embellissements à l'aide de CSS. Par exemple, dans Material Design Lite, le bouton avec l'effet d'ondulation contient deux éléments <span> supplémentaires pour implémenter l'ondulation elle-même. Si vous avez beaucoup de boutons, cela peut représenter un grand nombre d'éléments DOM et entraîner une dégradation des performances sur mobile. Si vous implémentez l'effet d'ondulation à l'aide d'un Worklet de peinture, vous vous retrouvez avec 0 élément supplémentaire et un seul Worklet de peinture. De plus, vous disposez d'un élément beaucoup plus facile à personnaliser et à paramétrer.

Un autre avantage de l'utilisation du Worklet Paint est que, dans la plupart des cas, une solution utilisant un Worklet de Paint est de petite taille en termes d'octets. Bien sûr, il y a un compromis: votre code de peinture s'exécute chaque fois que la taille du canevas ou l'un des paramètres changent. Par conséquent, si votre code est complexe et prend du temps, il peut entraîner des à-coups. Chrome travaille à déplacer les worklets de peinture du thread principal afin que même les worklets de peinture de longue durée n'affectent pas la réactivité du thread principal.

Pour moi, le plus intéressant est que le Worklet de peinture permet un polyfill efficace des fonctionnalités CSS qu'un navigateur ne possède pas encore. Par exemple, vous pouvez utiliser un polyfill pour les dégradés coniques jusqu'à ce qu'ils soient disponibles en mode natif dans Chrome. Autre exemple : lors d'une réunion CSS, il a été décidé que vous pouvez désormais utiliser plusieurs couleurs de bordure. Au cours de la réunion, mon collègue Ian Kilpatrick a écrit un polyfill pour ce nouveau comportement CSS à l'aide d'un Worklet de peinture.

Penser "out of the box"

La plupart des gens commencent à penser aux images de fond et aux images de bordure lorsqu'ils s'informent sur le Worklet de peinture. Un cas d'utilisation moins intuitif du worklet de peinture est mask-image pour donner aux éléments DOM des formes arbitraires. Par exemple, un diamant :

Élément DOM en forme de losange.
Élément DOM en forme de losange.

mask-image utilise une image de la taille de l'élément. Dans les zones où l'image du masque est transparente, l'élément est transparent. Zones dans lesquelles l'image de masque est opaque, l'élément opaque.

Disponible dans Chrome

Le worklet Paint est disponible dans Chrome Canary depuis un certain temps. Avec Chrome 65, il est activé par défaut. N'hésitez pas à tester les nouvelles possibilités offertes par le nouveau module de peinture et à nous montrer ce que vous avez créé. Pour plus d'inspiration, consultez la collection de Vincent De Oliveira.