7 points par GN⁺ 2024-05-06 | 1 commentaires | Partager sur WhatsApp
  • 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 --t plutôt qu’avec des keyframes redevient pratique
  • En combinant @property --t avec une animation racine d’une durée de 86400000ms, le temps augmente à la milliseconde près et peut être relié au calcul de propriétés comme rotate ou translate
  • 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() et round(), 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-doodle fournit des notations abrégées comme @t, @T et @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 --t pour suivre le temps en millisecondes
  • L’exemple de base déclare @property --t comme un <integer>, le fait varier de 0 à 86400000 dans @keyframes tick, puis applique à :root une animation 86400000ms linear infinite
  • --t augmente de 1 toutes les millisecondes et vaut 1000 au bout d’une seconde
  • counter() permet d’afficher à l’écran la valeur actuelle de --t
  • En reliant à une propriété un calcul basé sur --t, comme rotate: 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)))

Transformer une valeur temporelle croissante en valeur d’animation

  • La valeur --t augmente continuellement dans une seule direction
  • Une valeur d’angle peut dépasser 360deg sans 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 --t lui-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 avec cubic-bezier()
  • On commence par limiter --t à une valeur comprise entre 0 et 1
    • --t01: calc(min(1000, var(--t)) / 1000) passe de 0 à 1 en 1 seconde
  • ease-out-cubic

    • ease-out-cubic repose sur la formule 1 - 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-elastic repose sur la formule pow(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 dans translate pour l’appliquer à un déplacement de 270px

Réduire les formules avec css-doodle

  • Plus les formules deviennent complexes, plus var() et calc() rendent le code difficile à lire
  • La dernière version de css-doodle a ajouté la fonction @t, qui représente --t et accepte directement de simples expressions mathématiques en argument
  • L’expression rotate se raccourcit ainsi
    • Avant : rotate: calc(mod(var(--t) / 1000, 10) * 5deg)
    • css-doodle : rotate: @t(/1000, %10, *5deg)
  • 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)
  • 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 donc rotate: 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

 
GN⁺ 2024-05-06
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-delay
    Par exemple, animation-delay: -1500ms démarre l’animation immédiatement tout en sautant au point de 1,5 seconde
    Si 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...

    • Dans ce cas, ça ne rendrait pas aussi possible des animations basées sur l’offset de défilement à la iOS ?
  • 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étriques
    CSS 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 ?

    • Il ne faut pas oublier que les animations CSS bénéficient de l’accélération matérielle
      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é
    • CSS n’est pas une couche de dessin
      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”
    • Un avantage qui me vient à l’esprit est la séparation entre la logique applicative et la présentation
      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
    • JS est fondamentalement mono-thread, donc même s’il y a les workers, tout ce qui permet de déporter du travail ailleurs est bon à prendre pour garder une UI à un framerate stable
    • Attendons de voir quand WebSocket arrivera dans CSS !
      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 @property utilisé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-value
    Chrome utilise initial-value si la variable CSS n’est pas définie ou si elle reçoit une valeur invalide
    Firefox n’utilise initial-value que si la variable n’est pas définie
    Dans 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

    • Si vous voulez tester la démo dans Firefox, allez dans about:config et activez layout.css.properties-and-values.enabled
    • Voici comment définir une valeur de propriété depuis JavaScript quand c’est nécessaire
      Ré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 @property n’est pas encore activée, cela peut servir de polyfill
  • Sur les versions récentes de Chrome pour Android, chose intéressante, les démos antérieures à mod() fonctionnent même sans Experimental feature flags, mais mod() et sin() ne font rien

  • Si 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

    • Ce n’est pas simplement la courbe d’animation de CSS ?
  • 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 pas
    Si 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

    • Quelqu’un a déjà utilisé RTCode ? Ça a l’air intéressant, mais en regardant la page d’accueil, je ne sais pas trop si c’est un remplaçant de CodePen avec support temps réel, ou si ça peut aussi servir en production