Techniques d’animation CSS basées sur le temps
(yuanchuan.dev)- Avec la prise en charge croissante des fonctions mathématiques CSS, l’approche consistant à calculer l’état d’une animation avec une variable de tick temporel
--tplutôt qu’avec des keyframes redevient pratique - En combinant
@property --tavec une animation racine d’une durée de86400000ms, le temps augmente à la milliseconde près et peut être relié au calcul de propriétés commerotateoutranslate - Avec
step(), on peut limiter le frame rate à 8fps, 24fps ou 60fps, et grâce aux optimisations des navigateurs, une fréquence de mise à jour supérieure à 60fps ne pose généralement pas de problème - Avec
min(),mod(),sin(),pow()etround(), on peut limiter, répéter, faire osciller et adoucir les valeurs temporelles afin d’exprimer des easings personnalisés et des déplacements sans keyframes css-doodlefournit des notations abrégées comme@t,@Tet@TS, ce qui permet d’écrire plus brièvement les démos riches en formules mathématiques et d’expérimenter rapidement
Créer un tick temporel comme variable CSS
- Les anciennes approches avaient un champ d’application limité, car il était difficile d’effectuer des calculs mathématiques complexes en CSS
- Aujourd’hui, CSS prend en charge les fonctions mathématiques nécessaires, comme
mod(),round()et les fonctions trigonométriques, ce qui permet de réessayer les animations basées sur le temps - Pour consulter les démos, il peut être nécessaire d’activer les Experimental feature flags
Fonctionnement de base
- Comme en JavaScript, CSS ne peut pas démarrer directement un timer, mais avec la CSS Houdini API, on peut définir une variable personnalisée
--tpour suivre le temps en millisecondes - L’exemple de base déclare
@property --tcomme un<integer>, le fait varier de0à86400000dans@keyframes tick, puis applique à:rootune animation86400000ms linear infinite --taugmente de1toutes les millisecondes et vaut1000au bout d’une secondecounter()permet d’afficher à l’écran la valeur actuelle de--t- En reliant à une propriété un calcul basé sur
--t, commerotate: calc(var(--t) * .001turn), la valeur change avec le temps et se transforme en animation
Contrôle du frame rate
- Une fréquence de mise à jour de 60fps suffit pour des animations fluides
- Grâce aux optimisations du rendu des navigateurs, une fréquence de mise à jour supérieure à 60fps ne pose généralement pas de problème
- Si nécessaire, on peut limiter directement le frame rate en utilisant step() dans
animation-timing-function- 8fps :
step(calc(86400000/(1000/8))) - 24fps :
step(calc(86400000/(1000/24))) - 60fps :
step(calc(86400000/(1000/60)))
- 8fps :
Transformer une valeur temporelle croissante en valeur d’animation
- La valeur
--taugmente continuellement dans une seule direction - Une valeur d’
anglepeut dépasser360degsans problème, mais toutes les propriétés CSS ne traitent pas les valeurs de manière cyclique translate: calc(var(--t) * .001px)déplace la boîte continuellement vers la droite-
S’arrêter avec min()
- Pour arrêter le mouvement dès qu’un offset donné est atteint, on peut utiliser min()
translate: min(270px, calc(var(--t) * .5px))limite la valeur de déplacement à 270px- Pour contrôler précisément la durée, on peut limiter
--tlui-même translate: calc(min(3000, var(--t)) * (270px / 3000))- Ce calcul fait parcourir 270px en 3 secondes
-
Répéter avec mod()
- Pour que la boîte reparte du début après s’être déplacée vers la droite, on utilise mod()
translate: calc(mod(var(--t)/4, 270) * 1px)permet de répéter l’offset
-
Créer un aller-retour avec sin()
- Un mouvement aller-retour peut être exprimé avec sin()
- On relie la valeur temporelle à une fonction trigonométrique, par exemple
translate: calc(sin(mod(var(--t)/135, 270)) * 135px)
Fonctions d’easing personnalisées
- Avec les fonctions mathématiques CSS et la variable
--t, on peut créer des easings personnalisés parfois difficiles à obtenir aveccubic-bezier() - On commence par limiter
--tà une valeur comprise entre0et1--t01: calc(min(1000, var(--t)) / 1000)passe de 0 à 1 en 1 seconde
-
ease-out-cubic
ease-out-cubicrepose sur la formule1 - pow(1 - t, 3)- On crée ensuite une valeur
--ease-out-cubic, puis on la relie àtranslate: calc(var(--ease-out-cubic) * 270px)
-
ease-out-elastic
ease-out-elasticrepose sur la formulepow(2, -10t) * sin((10t - .75) * 2/3 * PI) + 1- De la même manière, on crée une valeur
--ease-out-elastic, puis on la multiplie danstranslatepour l’appliquer à un déplacement de 270px
Réduire les formules avec css-doodle
- Plus les formules deviennent complexes, plus
var()etcalc()rendent le code difficile à lire - La dernière version de css-doodle a ajouté la fonction @t, qui représente
--tet accepte directement de simples expressions mathématiques en argument - L’expression
rotatese raccourcit ainsi- Avant :
rotate: calc(mod(var(--t) / 1000, 10) * 5deg) css-doodle:rotate: @t(/1000, %10, *5deg)
- Avant :
- On peut créer des animations basées sur les mathématiques avec un code court, sans écrire de keyframes
- En modifiant les paramètres, on peut expérimenter rapidement de nouveaux résultats
-
@T et @TS
- En plus de
@t, @T en majuscule représente les time ticks depuis le début de la journée - @TS est un shorthand de
@t(/1000)et suit le temps en secondes - Un exemple d’horloge est disponible sur CodePen
- Trotteuse :
rotate: @TS(*6, %360deg) - Aiguille des minutes :
rotate: @TS(/60, *6, %360deg) - Aiguille des heures :
rotate: @TS(/60, /12, *6, %360deg)
- En plus de
-
Créer le saut de la trotteuse avec round()
- Pour faire avancer la trotteuse par à-coups, on peut utiliser round()
- Le troisième paramètre de
round()indique l’intervalle d’arrondi - Dans une horloge, un pas vaut
360 / 60 = 6deg, on utilise doncrotate: round(down, @TS(*6, %360deg), 6deg)
Démos et points d’application
- Un exemple animant à la fois la couleur et la position est disponible sur CodePen
- Un exemple d’animation de la couleur du texte est disponible sur CodePen
- Les keyframes peuvent sembler plus intuitives, mais dans les scènes de démonstration avec beaucoup de calculs mathématiques et de variables d’entrée, l’approche consistant à utiliser le temps comme variable permet plus facilement de produire des résultats variés
- Une démo d’animation de couleur de texte a été ajoutée dans la mise à jour du 17 mai 2024
1 commentaires
Discussions sur Hacker News
Si vous voulez contrôler la progression en JS, il est aussi recommandé d’utiliser une valeur négative de
animation-delayPar exemple,
animation-delay: -1500msdémarre l’animation immédiatement tout en sautant au point de 1,5 secondeSi cette valeur est pilotée en JS, on peut pratiquement faire du scrubbing d’une animation CSS depuis JS, ce qui permet d’aligner toutes les animations sur une boucle de tick compute-update-render façon moteur de jeu
https://developer.mozilla.org/en-US/docs/Web/CSS/animation-d...
Dès qu’on va un peu au-delà d’une simple fonction d’easing ou de keyframes basiques sur un ou deux canaux, on atteint vite les limites de cette approche
J’utilise Theatre.js depuis quelques années et j’en suis plutôt satisfait
C’est une bibliothèque divisée entre une interface studio pour éditer des keyframes et des courbes de Bézier sur une timeline, et un runtime qui interpole ces keyframes en fonction de cette timeline
Si vous devez faire bouger plusieurs animations de façon synchronisée, ça vaut le coup d’essayer
https://www.theatrejs.com/
C’est très impressionnant, mais j’ai aussi l’impression qu’on ne fait que se rapprocher maintenant de ce qu’on pouvait déjà faire avec Flash il y a des décennies
Je ne souhaite pas le retour de Flash, mais j’aimerais qu’il existe des outils plus conviviaux pour créer des animations CSS
CSS prend désormais assez bien en charge des fonctions mathématiques comme
mod(),round()et les fonctions trigonométriquesCSS ne peut pas démarrer un timer comme JavaScript, mais aujourd’hui on peut définir via l’API CSS Houdini des variables personnalisées qui suivent le temps à la milliseconde près
Mais puisqu’on a JavaScript, pourquoi a-t-on besoin de tout ça ?
La couche de dessin ne devrait-elle pas se concentrer sur les primitives de rendu ? Pourquoi y ajouter davantage de fonctionnalités de plus haut niveau ?
Si vous faites tourner des fonctions trigonométriques en JS pour une animation, vous manipulez probablement aussi le DOM pour essayer de mettre à jour l’UI à chaque milliseconde, et le coût est très élevé
Le rendu est piloté par le DOM, structuré en HTML ou mis à jour en JS
CSS se contente de se greffer dessus en passant des paramètres au moteur de peinture sur ce qu’il doit faire avec le DOM, en remplaçant les valeurs par défaut
On peut ajouter des blocs DOM, modifier le comportement du positionnement avec des algorithmes complexes, et même les marges sont loin d’être si simples
Je ne pense pas que ce soit une solution brillante, mais cela semble venir de la volonté d’éviter de devoir mettre à jour le code HTML pour changer des couleurs pendant le rendu du DOM
C’est probablement parce que modifier le code HTML n’était pas du tout agréable, mais à mon avis cela résolvait le mauvais problème dès le départ
Il faut aussi se souvenir que c’était l’époque des “webmasters”
C’est plus lisible quand la logique de fetch n’est pas mélangée avec la logique géométrique d’un élément DOM de chargement, par exemple le calcul d’un angle de rotation et l’application de styles inline
Je sais bien qu’aujourd’hui ce n’est plus très populaire de garder le chocolat et le beurre de cacahuète séparés, mais ce type de convention trace des frontières qui aident à deviner où le code se trouve et où il devrait aller
Blague à part, CSS est déclaratif, et il est naturel qu’il y ait un recouvrement entre les problèmes qu’on peut résoudre en JS et en CSS
Les nouvelles fonctionnalités ajoutées récemment à CSS sont vraiment enthousiasmantes, et réduire la quantité de code interprété est une bonne chose
La propriété CSS personnalisée
@propertyutilisée dans l’article est excellente, et la prise en charge navigateur est de 88 % [1]Il faut toutefois faire attention aux différences de comportement entre navigateurs lors de la définition d’une valeur de remplacement
initial-valueChrome utilise
initial-valuesi la variable CSS n’est pas définie ou si elle reçoit une valeur invalideFirefox n’utilise
initial-valueque si la variable n’est pas définieDans la plupart des projets, ce ne sera pas un problème, mais sur un projet récent, nous avons dû définir la valeur par défaut en JavaScript sur Firefox pour harmoniser les différences d’implémentation entre navigateurs
[1] https://caniuse.com/?search=%40property
about:configet activezlayout.css.properties-and-values.enabledRécupérer une valeur de propriété définie dans
:root{}:getComputedStyle(document.documentElement).getPropertyValue("--some-custom-css-var");Définir une valeur de propriété :
document.documentElement.style.setProperty("--some-custom-css-var", "some value");Dans les navigateurs où la prise en charge de CSS
@propertyn’est pas encore activée, cela peut servir de polyfillSur les versions récentes de Chrome pour Android, chose intéressante, les démos antérieures à
mod()fonctionnent même sans Experimental feature flags, maismod()etsin()ne font rienSi je voulais ça depuis un moment, c’était pour créer quelque chose comme du box-breathing
Je voulais pouvoir ajuster à ma convenance, et selon l’effet recherché, toutes les phases inspirer-pause-expirer-pause
Sans une approche comme celle de cette page, je n’avais pas trouvé de moyen de l’implémenter sans JS, ou sans une complexité absurde
Des feuilles de style, quand même ! Les démos de cette page sont vraiment créatives
L’article est vraiment très bien écrit
Chrome n’a pas encore livré la prise en charge de CSS
mod()dans une version stable, donc à moins d’utiliser une préversion de Chrome, la plupart des exemples de la page ne s’animent pasSi vous n’avez pas besoin d’un contrôle par tick et que vous voulez juste des animations CSS fluides dans tous les navigateurs, la technique FLIP est utile
https://medium.com/outsystems-experts/flip-your-60-fps-anima...
L’animation en bas de la page est très impressionnante
Elle ressemble davantage à une vidéo qu’à une suite d’éléments individuels
https://RTCode.io est un playground web en temps réel qui fonctionne sans rechargement ni réinitialisation d’état, ce qui peut beaucoup aider les personnes qui travaillent sur des animations ou transitions CSS
La démo est sur https://RTCode.io
Pour importer un Pen, il suffit de remplacer le nom d’hôte de l’URL par
xcodepen.io