Animations Chromatiques

Animer la teinte OKLCH avec @property (Houdini) — onde, stagger, combinaisons

Introduction

Cela t'intéresse ? Cet article s'adresse à quelqu'un qui connaît déjà les variables CSS (--ma-variable) et les animations (@keyframes). La matrice de lettres polygonales n'est pas un prérequis, mais elle sert de terrain de jeu.

Prérequis : bases de CSS, notions d'OKLCH (voir l'accueil du lab), lecture de Matrice SQI recommandée.

L'expérience : Est-il facile et amusant 'animer une propriété CSS custom de manière fluide et synchronisée ? Peut-on faire du CSS pur pour créer un effet d'onde encapsulé sur un mot, et de combiner plusieurs animations indépendantes sur un même élément.

Problématique

On veut animer la couleur d'un mot lettre par lettre, il existe plusieurs façons de créer des effets qui ressemblent à une onde chromatique qui traverse le texte de gauche à droite. Chaque lettre doit avoir sa propre teinte à chaque instant.

Première tentative : animer background-color directement. Ça fonctionne… mais l'ombre drop-shadow reste figée. C'est moche. Les deux propriétés ne sont pas synchronisées et elles ne partagent pas la même source de vérité (la teinte).

Deuxième tentative : stocker la teinte dans une variable CSS --dynamic-h et l'animer dans un @keyframes. Résultat : rien ne bouge. Le navigateur voit la variable comme une chaîne de texte opaque. Il ne sait pas qu'il s'agit d'un nombre. Il passe brutalement de la valeur de départ à la valeur d'arrivée, sans interpoler. Mission accomplie ? Pas vraiment.

/* Ce code ne produit PAS de transition */
@keyframes hue-test {
  0% { --dynamic-h: 330; }
  100% { --dynamic-h: 690; }
}
/* Le navigateur traite --dynamic-h comme du texte,
pas comme un nombre. Zéro interpolation. */

Sans un mécanisme pour typer la variable, l'animation de teinte coordonnée entre plusieurs propriétés est impossible en CSS pur.

Le Concept

L'API @property (Houdini CSS) permet d'enregistrer une propriété CSS custom avec un type. Une fois typée, le navigateur peut interpoler entre deux valeurs exactement comme il le ferait avec width ou opacity.

La syntaxe déclare trois choses :

Couplé à oklch(L C H), on peut animer H (la teinte) sur un tour complet du cercle chromatique (0 → 360). En OKLCH, contrairement à HSL, la luminosité perçue reste constante pendant ce tour. L'onde est propre, sans saut visuel.

Le stagger (décalage temporel) consiste à donner à chaque lettre un animation-delay différent. Chaque élément joue la même animation, mais à un instant décalé. Le résultat est une propagation spatiale de la couleur — une onde.

Le Code

L'API @property

Une règle at-rule CSS qui enregistre une propriété custom avec un type natif, permettant son interpolation par le moteur d'animation.

Quand l'utiliser ?

Dès qu'on veut animer une variable CSS de manière fluide — particulièrement utile quand plusieurs propriétés (filter, background, border-color…) doivent réagir à la même valeur en temps réel.

Implémentation

/* 1. Typer la variable */
@property --dynamic-h {
  syntax: '<number>';
  inherits: true;
  initial-value: 330;
}

/* 2. Animer la variable */
@keyframes hue-wave {
  0% { --dynamic-h: var(--h-brand); }
  100% { --dynamic-h: calc(var(--h-brand) + 360); }
}

/* 3. Connecter ombre + couleur à la MÊME variable */
.brutal-word > span {
  filter: drop-shadow(var(--shadow-offset) var(--shadow-offset) 0
    oklch(40% var(--c-neon) var(--dynamic-h)));
}
.brutal-word > span::after {
  background: oklch(var(--l-action) var(--c-neon) var(--dynamic-h));
}

/* 4. Appliquer l'animation avec stagger */
.brutal-word.chromatic-wave > span {
  animation: hue-wave 4s infinite steps(15);
}
.brutal-word.chromatic-wave > span:nth-child(1) { animation-delay: 0.0s; }
.brutal-word.chromatic-wave > span:nth-child(2) { animation-delay: 0.2s; }
/* … */

Pourquoi ce choix ?

Animer background-color directement ne synchronise pas l'ombre. Animer une variable CSS sans @property ne produit aucune interpolation. @property est le seul mécanisme CSS natif qui résout les deux problèmes à la fois.

Démonstration

Le mot COMBAT en onde chromatique saccadée (steps(15)) :

Chaque polygone a sa propre teinte à chaque instant T. L'ombre drop-shadow suit exactement la même équation OKLCH — aucune désynchronisation possible car les deux lisent --dynamic-h.

Les Règles

Toujours typer la variable avec @property

Sans syntax: '<number>', l'interpolation est impossible. C'est le prérequis absolu. Une variable non typée ne peut pas être animée de manière fluide.

Une seule variable = une seule source de vérité

Toutes les propriétés visuelles liées à la même valeur (couleur, ombre, bordure…) doivent lire la même variable. Ne jamais dupliquer la valeur dans plusieurs propriétés indépendantes.

Préférer OKLCH pour les animations de teinte

En HSL, un tour de cercle chromatique produit des sauts de luminosité perçue. En OKLCH, la luminosité reste perceptuellement constante. Pour une onde propre, OKLCH est obligatoire.

Respecter prefers-reduced-motion

Toute animation doit être désactivée si l'utilisateur a activé la préférence système de mouvement réduit. La règle @media (prefers-reduced-motion: reduce) est non négociable.

Conclusion

@property transforme une variable CSS en donnée typée, animable nativement par le moteur du navigateur. Combiné à OKLCH, il permet des effets chromatiques fluides et perceptuellement cohérents, sans JavaScript.

Les trois règles essentielles : typer la variable, une seule source de vérité, OKLCH pour les teintes.

La Suite

Quelques pistes à explorer depuis ici :

Démos supplémentaires

Flow · linear
Pulse + Couleur
inverted + wave
breathe + flow