API CSS Paint

Nouvelles possibilités dans Chrome 65

L'API CSS Paint (également appelée "CSS Custom Paint" ou "Worklet de peinture d'Houdini") est activée par défaut à partir de Chrome 65. De quoi est-il question ? Que pouvez-vous en faire ? Et comment cela fonctionne-t-il ? Eh bien, continuez votre lecture, d'accord...

L'API CSS Paint vous permet de générer une image de manière programmatique chaque fois qu'une propriété CSS en attend une. 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 CSS intégrées comme linear-gradient(). Au lieu de les utiliser, vous pouvez désormais utiliser paint(myPainter) pour référencer un Worklet de peinture.

Écrire un atelier de peinture

Pour définir un worklet de peinture appelé myPainter, nous devons charger un fichier de worklet 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 pour un CanvasRenderingContext2D tel que nous le connaissons à partir de <canvas>. Si vous savez comment dessiner dans un <canvas>, vous pouvez utiliser un worklet de peinture. geometry nous indique la largeur et la hauteur de la toile à notre disposition. properties. Je vous l'expliquerai plus loin dans cet article.

Comme exemple d'introduction, écrivons un workflow de peinture en damier et utilisons-le comme image de fond d'un <textarea>. (J'utilise une zone de texte, car elle 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 regarder 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 par rapport à l'utilisation d'une image de fond courante est que le motif est redessiné à la demande, chaque fois que l'utilisateur redimensionne la zone de texte. Cela signifie que l'image de fond est toujours aussi grande que nécessaire, y compris la compensation pour les écrans haute densité.

C'est plutôt cool, mais c'est aussi assez statique. Voudrions-nous écrire un nouveau worklet chaque fois que nous voulons le même schéma, mais avec des carrés de tailles différentes ? La réponse est non !

Paramétrer votre worklet

Heureusement, le worklet Paint peut accéder à d'autres propriétés CSS, et 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 aux 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 damier. Mais ce n'est pas tout : nous pouvons désormais accéder aux outils de développement et

Navigateurs non compatibles avec le worklet

Au moment de la rédaction de ce document, seul Chrome dispose du worklet de peinture. Bien que tous les autres fournisseurs de navigateurs nous envoient des signaux positifs, il n'y a pas beaucoup de progrès. Pour vous tenir informé, vérifiez régulièrement la page 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 worklet de peinture n'est pas compatible. Pour vous assurer que tout fonctionne comme prévu, vous devez ajuster votre code à deux endroits: dans le CSS et dans JavaScript.

Pour détecter la prise en charge du worklet de peinture en JavaScript, vérifiez l'objet CSS : js if ('paintWorklet' in CSS) { CSS.paintWorklet.addModule('mystuff.js'); } Pour le CSS, deux options s'offrent à vous. Vous pouvez utiliser @supports:

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

Une astuce plus compacte consiste à utiliser le fait que CSS invalide et ignore par la suite une déclaration de propriété entière si elle contient une fonction inconnue. Si vous spécifiez deux fois une propriété, 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 écrasera la première. Dans les navigateurs non compatibles avec le worklet de peinture, la deuxième déclaration n'est pas valide et sera supprimée, laissant la première en vigueur.

Peinture Polyfill CSS

Pour de nombreuses utilisations, il est également possible d'utiliser CSS Paint Polyfill, qui ajoute la prise en charge des Worklets CSS Custom Paint et Paint aux navigateurs modernes.

Cas d'utilisation

Les cas d'utilisation des ateliers de peinture sont nombreux, certains étant plus évidents que d'autres. L'une des méthodes les plus évidentes consiste à utiliser un worklet de peinture pour réduire la taille de votre DOM. Souvent, les éléments sont ajoutés uniquement pour créer des embellissements à l'aide de CSS. Par exemple, dans Material Design Lite, le bouton avec 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 s'ajouter à un grand nombre d'éléments DOM et nuire aux performances sur mobile. Si vous implémentez l'effet d'ondulation à l'aide d'un workflow de peinture, vous n'obtenez aucun élément supplémentaire et un seul Worklet de peinture. De plus, votre solution est beaucoup plus facile à personnaliser et à paramétrer.

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

Pour moi, la perspective la plus intéressante est que le worklet de peinture permet un polyfilling efficace des fonctionnalités CSS qu'un navigateur ne possède pas encore. Par exemple, vous pouvez polyfiller les dégradés coniques jusqu'à ce qu'ils arrivent dans Chrome de manière native. Autre exemple: dans une réunion CSS, il a été décidé que vous pouviez maintenant avoir plusieurs couleurs de bordure. Pendant cette réunion, mon collègue Ian Kilpatrick a écrit un polyfill pour ce nouveau comportement CSS à l'aide d'un worklet de peinture.

Sortir des sentiers battus

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

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

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

Désormais dans Chrome

Le atelier de peinture est utilisé dans Chrome Canary depuis un certain temps. Dans Chrome 65, elle est activée par défaut. Testez les nouvelles possibilités qu'offrent les ateliers de peinture et montrez-nous ce que vous avez créé ! Pour plus d'inspiration, consultez la collection de Vincent De Oliveira.