API CSS Paint

Nouvelles possibilités dans Chrome 65

L'API CSS Paint (également appelée "CSS Custom Paint" ou "Worklet de Houdini") est activée par défaut à partir de Chrome 65. De quoi est-il question ? Que pouvez-vous en faire ? Comment cela fonctionne-t-il ? Poursuivez la lecture...

L'API CSS Paint vous permet de générer une image de manière programmatique lorsqu'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 CSS intégrées telles que linear-gradient(). Au lieu de les utiliser, vous pouvez maintenant utiliser paint(myPainter) pour référencer un worklet de peinture.

Écrire un Worklet de peinture

Pour définir un Worklet Paint appelé myPainter, nous devons charger un fichier Paint 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 Paint:

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

registerPaint('myPainter', MyPainter);

Dans le rappel paint(), nous pouvons utiliser ctx de la même manière qu'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 du canevas dont nous disposons. properties Je l'expliquerai plus loin dans cet article.

À titre d'exemple d'introduction, nous allons écrire un workflow de peinture en damier et l'utiliser 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 être familier. Pour voir la démonstration en direct, cliquez ici.

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

La différence par rapport à l'utilisation d'une image de fond courante ici 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 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. Veux-tu écrire un nouveau workflow chaque fois que nous voulions le même modèle, 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, où 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 sont 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 différents types de damier. 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 la fonctionnalité Paint

Au moment de la rédaction de ce document, seul Chrome dispose d'un Worklet de peinture. Bien que tous les autres fournisseurs de navigateurs proposent des signaux positifs, les progrès sont limités. Pour vous tenir à jour, consultez régulièrement la page Is Houdini Ready Yet ? régulièrement. En attendant, veillez à utiliser l'amélioration progressive pour continuer à exécuter votre code, même si le joblet 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 le code 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 par la suite une déclaration de propriété entière si celle-ci 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 de peinture, la deuxième déclaration n'est pas valide et sera supprimée, ce qui laisse la première déclaration en vigueur.

Polyremplissage de peinture CSS

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

Cas d'utilisation

Il existe de nombreux cas d'utilisation des worklets de peinture, certains d'entre eux plus évidents que d'autres. L'une des plus évidentes consiste à utiliser un peinture pour réduire la taille de votre DOM. Souvent, les éléments sont ajoutés uniquement pour créer des décorations à 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 s'ajouter à un nombre important 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 de quelque chose de 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. Ainsi, si votre code est complexe et prend beaucoup de temps, cela peut entraîner des à-coups. Chrome travaille sur le déplacement des travaillets de peinture hors du thread principal afin que même ceux dont l'exécution est longue 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 émuler les dégradés coniques jusqu'à ce qu'ils arrivent de manière native dans Chrome. Autre exemple: lors d'une réunion CSS, il a été décidé que vous pouviez désormais avoir 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.

Sortir des sentiers battus

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, qui permet de donner des formes arbitraires aux éléments DOM. Par exemple, un losange:

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

mask-image prend une image dont la taille correspond à celle de l'élément. Les zones dans lesquelles l'image de masque est transparente, l'élément l'est également. Zones dans lesquelles l'image de masque est opaque, l'élément opaque.

Désormais disponible dans Chrome

Le Worklet de peinture est présent dans Chrome Canary depuis un certain temps. Avec Chrome 65, elle est activée par défaut. Allez-y, essayez les nouvelles possibilités que vous offre le Worklet de peinture et montrez-nous ce que vous avez créé ! Besoin d'inspiration ? Découvrez la collection de Vincent De Oliveira.