Utilisation de CSS Houdini : Créez des effets personnalisés non disponibles en CSS standard
Utilisation de CSS Houdini : Créez des effets personnalisés non disponibles en CSS standard
Essayez de faire quelque chose d'apparemment simple : animer en douceur un angle dans un conic-gradient() au survol. Vous définissez une custom property --kat: 0deg, vous ajoutez transition: --kat 0.4s, vous survolez avec la souris — et rien ne se passe. Le gradient change brutalement, comme si transition n'existait pas. Ce n'est pas un bug dans votre code. Pour le navigateur, --kat est une simple chaîne de caractères opaque — il ne sait pas que c'est un angle, donc il n'a aucun moyen de l'interpoler entre 0deg et 180deg. Il sait interpoler des nombres, des couleurs, des longueurs — parce qu'il connaît leur type. Les custom properties, contrairement aux propriétés CSS natives, n'ont aucun type. C'est exactement dans cette lacune que s'inscrit CSS Houdini.
Ce qu'est vraiment Houdini
C'est un malentendu fréquent : Houdini n'est ni une seule technologie, ni même une seule spécification. C'est un ensemble parapluie regroupant plusieurs propositions indépendantes au sein du W3C, unifiées par une seule idée — exposer des fragments du moteur de rendu (analyse des valeurs, mise en page, dessin, animation) qui étaient auparavant totalement cachés au développeur. Plutôt que d'attendre l'apparition en CSS d'une propriété toute faite qui fait exactement ce dont vous avez besoin, vous obtenez un point d'ancrage bas niveau sur une étape précise de ce processus.
Ces points d'ancrage sont mis en œuvre sous forme de worklets — de petits modules JavaScript, conceptuellement proches des Web Workers, mais conçus spécifiquement pour le pipeline de rendu. Un worklet s'exécute en dehors du thread principal, n'a accès ni à window, ni à document, ni au DOM, et ne communique que via un contrat strictement défini (par exemple paint(ctx, size, properties)). Ce n'est pas une limitation due à la paresse des auteurs de la spécification — c'est une décision architecturale délibérée. Grâce à cette isolation, le moteur peut exécuter les worklets en parallèle, sur des threads séparés, et mettre le résultat en cache sans risquer que votre code touche à quelque chose qu'il ne devrait pas. Cette même isolation explique pourquoi vous ne trouverez, à l'intérieur d'un worklet, ni Date, ni performance.now(), ni fetch — restreindre l'accès à une mesure précise du temps et au réseau est une protection délibérée contre les attaques de type timing/fingerprinting depuis le code de rendu de la page.
Les différentes parties de cet ensemble en sont aujourd'hui à des stades tout à fait différents : l'une fait partie de Baseline depuis longtemps, une autre ne fonctionne que dans Chrome, et une autre encore reste expérimentale après une décennie. Cette distinction est essentielle — et la plupart des articles d'introduction à Houdini l'ignorent, traitant l'ensemble comme une seule technologie prête à l'emploi.
Le seul Houdini qu'il vaut vraiment la peine d'utiliser aujourd'hui : @property
La CSS Properties and Values API résout exactement le problème évoqué en ouverture de cet article. Vous enregistrez une custom property avec un type précis (syntax), une valeur initiale et une information indiquant si elle doit être héritée — et à partir de ce moment, le navigateur la traite comme une valeur animable à part entière, et non comme une chaîne de caractères.
html
<div class="ring"></div>
css
@property --kat {syntax: '<angle>';inherits: false;initial-value: 0deg;}.ring {width: 200px;height: 200px;border-radius: 50%;background: conic-gradient(from var(--kat), #ff4d4d, #4d79ff, #4dff88, #ff4d4d);transition: --kat 0.6s ease;}.ring:hover {--kat: 180deg;}
Cet exemple fait réellement quelque chose que le CSS classique ne sait pas faire — sans @property, le transition ci-dessus ne fonctionnerait tout simplement pas, exactement comme dans le scénario du début de l'article. syntax: '<angle>' indique au navigateur que --kat est un angle, ce qui lui permet de l'interpoler en douceur ; inherits: false évite un héritage accidentel par les éléments enfants, ce qui, avec les custom properties, est souvent source de bugs difficiles à repérer.
Le même effet peut être obtenu depuis JavaScript, ce qui a du sens lorsque vous enregistrez des propriétés de façon dynamique (par exemple générées à partir de données) plutôt que codées en dur dans la feuille de styles :
javascript
if ('registerProperty' in CSS) {CSS.registerProperty({name: '--kat',syntax: '<angle>',inherits: false,initialValue: '0deg',});}
C'est la seule partie de Houdini qui a aujourd'hui le statut Baseline — elle fonctionne nativement dans Chrome, Firefox (à partir de la version 128) et Safari (à partir de 16.4), sans polyfill ni @supports. Si vous ne deviez retenir et mettre en œuvre qu'une seule chose de tout Houdini, ce serait celle-ci.
CSS Paint API : un worklet qui dessine pour de vrai
La Paint API permet de remplacer background par une fonction paint(), qui dessine directement sur l'élément en s'appuyant sur une interface allégée proche de Canvas 2D (PaintRenderingContext2D) — sans fillText, drawImage ni lecture de pixels. Ce qui distingue cette approche du simple dessin sur <canvas>, c'est inputProperties : le worklet déclare quelles custom properties il souhaite observer, et se redessine automatiquement uniquement lorsque l'une d'entre elles change — sans la moindre ligne de JavaScript dédiée à l'écoute d'événements.
Voici ci-dessous un motif de bandes obliques d'avertissement, dont l'angle est piloté par cette même propriété --kat enregistrée précédemment (ou plus précisément par sa variante locale --stripe-angle) — ce qui montre comment @property et la Paint API se complètent naturellement.
html
<div class="hazard"></div>
css
@property --stripe-angle {syntax: '<angle>';inherits: false;initial-value: 45deg;}.hazard {width: 100%;height: 120px;--stripe-angle: 45deg;background: paint(hazardStripes);transition: --stripe-angle 0.4s ease;}.hazard:hover {--stripe-angle: 135deg;}
javascript
// main.jsif ('paintWorklet' in CSS) {CSS.paintWorklet.addModule('hazard-stripes.js');}
javascript
// hazard-stripes.jsclass HazardStripesPainter {static get inputProperties() {return ['--stripe-angle'];}paint(ctx, size, properties) {const angle = properties.get('--stripe-angle').to('deg').value;const stripeWidth = 24;const { width, height } = size;const diagonal = Math.sqrt(width ** 2 + height ** 2) * 2;ctx.save();ctx.translate(width / 2, height / 2);ctx.rotate((angle * Math.PI) / 180);ctx.translate(-diagonal, -diagonal);let stripeIndex = 0;for (let x = 0; x < diagonal * 2; x += stripeWidth) {ctx.fillStyle = stripeIndex % 2 === 0 ? '#111111' : '#f5c400';ctx.fillRect(x, 0, stripeWidth, diagonal * 2);stripeIndex++;}ctx.restore();}}registerPaint('hazardStripes', HazardStripesPainter);
Il vaut la peine de remarquer properties.get('--stripe-angle').to('deg').value — c'est le CSS Typed OM, un autre élément de la même famille d'API. Plutôt que d'analyser une chaîne de caractères à la main (parseFloat, découpage du suffixe "deg"), vous obtenez une valeur typée CSSUnitValue, que vous convertissez explicitement vers l'unité dont vous avez besoin. Grâce au fait que l'angle des bandes dessinées ne dépend que de la custom property déclarée dans inputProperties, le moteur sait exactement quand il faut redessiner l'élément — la modification d'une propriété CSS non liée ailleurs sur la page ne déclenchera pas un nouvel appel à paint().
Layout API et Animation Worklet : la réalité en 2026
C'est ici que commence la partie dont la plupart des articles d'introduction à Houdini ne parlent pas. La CSS Layout API — le troisième pilier de cette même spécification, permettant de définir son propre algorithme de mise en page (l'équivalent d'un display: grid personnalisé) — existe à l'état de projet depuis 2015 et reste expérimentale après une décennie. Aucun navigateur stable ne l'a implémentée nativement ; elle n'est disponible que derrière des flags ou dans les origin trials de Chrome. Si vous tombez sur un article présentant display: layout(nom) comme une solution prête à l'emploi, vérifiez la date de publication — il s'agit probablement d'une démo d'origin trial vieille de plusieurs années, pas de quelque chose que vous pouvez faire fonctionner aujourd'hui dans le navigateur d'un utilisateur.
L'Animation Worklet devait offrir un contrôle total sur les animations pilotées par le scroll, exécutées en dehors du thread principal. Dans la pratique, seul Chromium le prend en charge. Plus important encore, son principal cas d'usage n'est plus une raison de recourir à Houdini, car le CSS a reçu une solution native au même problème : animation-timeline: scroll() permet aujourd'hui de lier une animation au scroll sans la moindre ligne de JavaScript et sans worklet, avec un support croissant dans les navigateurs. C'est une bonne illustration d'un schéma plus large : une partie des raisons pour lesquelles Houdini a vu le jour a fini par être intégrée directement dans la spécification CSS, plutôt que de rester une API bas niveau destinée aux développeurs.
Quand cela a du sens en pratique
- Utilisez
@propertysans hésiter, sans filet de sécurité — il a le statut Baseline, il n'a besoin ni de@supportsni de polyfill. - Traitez la Paint API comme un enrichissement progressif — enveloppez-la dans un
@supports(background: paint(x))avec un repli sensé en CSS pur (par exemple un simple gradient) pour Firefox, ou utilisez le polyfillcss-paint-polyfillsi l'effet doit fonctionner partout. - Ne basez aucune fonctionnalité de production sur la Layout API — c'est encore une expérimentation sans réel support des navigateurs, bonne pour s'amuser, pas pour une roadmap.
- Souvenez-vous que
paint()n'est pas du CSS « gratuit » — c'est votre propre code JavaScript, réexécuté à chaque changement desinputPropertiesdéclarées. Les calculs coûteux (génération de bruit, motifs procéduraux complexes) doivent être optimisés exactement comme une boucle de rendu sur<canvas>— il est facile de construire par mégarde une animation qui redessine l'élément entier image par image sur le thread principal de mise en page.
Conclusion
Houdini n'est pas une seule décision « utiliser ou non » — ce sont trois ou quatre décisions distinctes, avec des niveaux de risque différents. Premièrement, @property résout un problème réel et concret (animer des custom properties) et est aujourd'hui aussi sûr à utiliser que n'importe quelle autre propriété CSS — c'est la seule partie de cet écosystème qu'il vaut la peine d'adopter sans hésiter. Deuxièmement, la Paint API offre une réelle puissance (un worklet qui ne se redessine que lorsque les dépendances déclarées changent, sans écoute manuelle d'événements), mais exige un plan réfléchi pour les navigateurs sans support — c'est un outil d'enrichissement progressif, pas pour des fonctionnalités critiques. Troisièmement, la Layout API et l'Animation Worklet illustrent bien qu'une spécification, à elle seule, ne garantit pas son adoption — après des années, l'une attend toujours d'être implémentée, tandis que l'autre a été en partie remplacée par du CSS natif plus simple. Avant de vous tourner vers Houdini, vérifiez dans lequel de ces trois paniers se trouve exactement la partie dont vous avez besoin.