aller au contenu principal

Optimiser ses animations CSS

Choisir les bonnes propriétés à animer pour optimiser les performances


Publié le 23/02/2023 dans Blog
Crédit photo - undefined
Je suis en train d'écrire un cours complet sur Flexbox. Il y aura un module entier sur la compréhension de ce mode de layout, et un module 100% pratique avec plusieurs exercices.Tu peux consulter le programme et à te préinscrire dès maintenant ! ⬇️Se préinscrire

Récemment je me suis intéréssé au sujet des performances lorsqu'on fait des animations CSS, et je me suis rendu compte qu'il y avait beaucoup de choses à dire là-dessus.

J'ai donc décidé de rédiger cet article pour te partager ce que j'ai appris sur ce sujet, tu es partant ? 😊

Depuis mes débuts en tant qu'intégrateur, j'ai toujours adoré faire des animations en CSS. C'est un peu ça qui m'a fait aimé le CSS.

Cependant, je ne me suis jamais soucié des performances, ou alors très peu. La plupart du temps, quand j'avais besoin de faire une animation CSS, je la faisais sur la propriété concernée.

Un exemple : si je veux animer le changement de largeur d'un élément, j'anime la propriété width, et si je veux animer une ombre portée, j'anime la propriété box-shadow.

Jusque-là tout semble normal, mais comme on va le voir dans cet article, il vaut mieux animer certaines propriétés plutôt que d'autres !

Comme j'aime le faire, je vais prendre tout de suite un cas concret : l'animation d'une ombre portée au hover.

C'est quelque chose que j'ai énormément fait, et peut-être que toi aussi.

On verra ensuite d'autres exemples et on parlera un peu plus de la technique.

Le but de cet article est aussi de te sensibiliser sur le sujet, tu pourras alors appliquer de meilleures performances à (presque) toutes tes animations !


Alors je vais mettre les pieds dans le plat tout de suite : animer la propriété box-shadow n'est pas une bonne idée, car cela demande au navigateur de "repeindre" (on parlera un peu plus loin du "painting", une étape importante lors de la phase de rendu du navigateur) l'ombre à chaque frame de l'animation.

Pour optimiser les animations sur les ombres portées, on évitera d'animer la propriété box-shadow. On animera plutôt la propriété opacity, qui est, on verra plus loin dans l'article pourquoi, moins gourmande en ressources pour le navigateur.

Pour comprendre de façon visuelle les problèmes de performances qui sont provoqués par l'animation de la propriété box-shadow je te propose de regarder le graphique ci-dessous :

'Comparaison entre les recalculs de styles du navigateur lors d'animations sur les propriétés box-shadow et opacity'

Les barres bleues correspondent aux nombre de recalculs de styles / seconde effectués par le navigateur. Tu comprends donc que moins il y en a, mieux c'est.

Ce graphique est juste là pour te faire prendre conscience des différences entre ces deux propriétés lorsqu'on fait des animations dessus. On verra en fin d'article comment voir ces données de façon précise dans le volet performances du navigateur.

Animation d'une ombre portée en CSS

Reprenons avec notre exemple d'animation sur une ombre portée, avant de voir comment animer une ombre de façon optimisée à l'aide de la propriété opacity, je te propose de voir comment faire l'animation avec la "mauvaise méthode".

La "mauvaise" méthode

La mauvaise méthode est pourtant celle qui parait la plus évidente : si on veut animer une ombre portée, on anime la propriété concernée, comme ceci :

.box {
  box-shadow: 0 1px 2px rgb(0 0 0 / 10%);
  transition: all 0.6s cubic-bezier(0.165, 0.84, 0.44, 1);
}
.box:hover {
  box-shadow: 0 5px 15px rgb(0 0 0 / 30%);
}

Le code à l'avantage d'être clair et concis, on ne change que la valeur de la propriété box-shadow au hover. Mais comme expliqué plus haut, cette méthode n'est pas la plus optimisée.

Voici le résultat (tu peux passer ta souris sur le bloc) :

🐌

La "bonne" méthode

La solution ici pour optimiser les choses est d'animer la propriété opacity, qui est moins gourmande en ressources pour le navigateur.

Euh ok.. 🤔 mais comment faire cela ? On ne peut pas dire avec la propriété opacity de ne cibler que l'ombre portée d'un élément !

C'est vrai ! Pour tout de même avoir une animation sur l'ombre, il faut mettre l'ombre que l'on souhaite voir au hover non pas sur l'élément, mais sur un pseudo-élément ::after et le masquer avec une opacité à 0.

Tu vois où je veux en venir ? Au hover il suffit de passer l'opacité du pseudo-élément à 1, ce qui aura pour conséquence de faire apparaître l'ombre portée.

Il ne faudra pas oublier de mettre une transition sur le pseudo-élément !

Comment ça se passe si mon élément doit avoir une ombre portée par défaut, et qui doit changer au hover ?

Cela ne change pas grand chose, car au final on déplace seulement l'ombre qui était sur le hover en la mettant sur le pseudo-élément. Avec cette technique tu peux avoir une ombre portée par défaut qui sera visible même si on ne fait pas de hover dessus, celle-ci n'aura tout simplement pas besoin d'être animée.

Avec cette astuce, ton ombre portée est déjà créée, tu demandes simplement au navigateur de la "montrer" au hover. Ce n'est pas le cas lorsqu'on anime la propriété box-shadow, car on demande au navigateur de "peindre" l'ombre au hover.. tu saisis la différence ?

Voici le code complet pour y parvenir :

.box {
  position: relative;
  box-shadow: 0 1px 2px rgb(0 0 0 / 10%);
  transition: all 0.6s cubic-bezier(0.165, 0.84, 0.44, 1);
  /* Ici je donne une ombre par défaut */
}
.box::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  box-shadow: 0 5px 15px rgb(0 0 0 / 30%);
  /* Ici je donne une ombre au pseudo-élément,
  celle que je veux voir au hover */
  opacity: 0;
  /* Puis je la cache ! */
  transition: all 0.6s cubic-bezier(0.165, 0.84, 0.44, 1);
  /* Pour finir j'applique une transition */
}
.box:hover::after {
  opacity: 1;
  /* C'est au hover de mon élément,
  que l'ombre de mon pseudo-élément devient visible ! */
}

Voici le résultat (tu peux là aussi passer ta souris sur le bloc) :

🚀

Si tu veux aller voir le code complet avec les deux méthodes voici un CodePen qui reprend le code des deux méthodes.

Ok c'est chouette d'améliorer les performances, mais ça fait quand même beaucoup de CSS à écrire, et puis je ne vois pas vraiment de différences ! Les deux animations me semblent être aussi fluide l'une que l'autre.. 🤔

Oui, mais regarde, on a ici une animation optimisée, rappelle toi du graphique plus haut.

N'oublie pas aussi que sur ton ordinateur, l'animation de la propriété box-shadow peut ne pas provoquer de dégradations visuelles, mais pense aux utilisateurs sur mobile ! Pense aussi que le navigateur n'a pas que tes animations à gérer, il a aussi pas mal d'autres tâches à exécuter !

Donc pour résumer : il n'est pas possible d'animer de façon performante la propriété box-shadow, ni d'autres propriétés comme width ou height (on le verra après dans l'article).

Pour contourner cela, on a juste changé la propriété animée ! Ici ce n'est pas faire une animation sur un pseudo-élément qui compte, mais le fait de changer la propriété animée !

Discutons rapidement performance et technique

Ce cas pratique nous donne la possibilité de nous informer un peu plus sur le sujet des animations et des performances.

Le plupart des propriétés CSS peuvent être animées, car les valeurs qu'elles prennent sont numériques, c'est le cas entre autres de width, color, ou encore font-size.. Quand on anime ces propriétés, le navigateur va changer leurs valeurs de façon graduelle, frame par frame, c'est cela qui crée l'animation.

Cependant, toutes les animations ne seront pas toutes fluides comme on peut l'espérer, car certaines propriétés ont un "coût" différent lorsqu'on les anime.

Si on rentre dans des détails techniques : quand il y a un changement de style sur une propriété, le navigateur peut passer par 3 étapes pour faire le nouveau rendu :

  • Layout : le navigateur va recalculer les dimensions géométriques et la position de l'élément
  • Painting : le navigateur recalcule les styles de l'élément, cela comprend la couleur de fond, les ombres..
  • Composition : le résultat final est rendu en pixels à l'écran, des transformations CSS sont appliquées si elles existent

Ces étapes font partie du "Pixel Rendering pipeline", le process de rendu des pixels sur une page Web.

Pourquoi utiliser autant que possible les propriétés transform et opacity pour les animations ? Car les changements qui interviennent sur ces deux propriétés ne font pas intervenir les étapes de "layout" et de "painting".

Ok je veux bien, mais parfois je n'ai pas le choix.. je suis obligé d'animer des propriétés comme font-size, background-color ou width !

Oui parfois tu ne pourras pas faire autrement, si une propriété est animable, et la plupart le sont, c'est que tu peux les animer ! Prend juste conscience que tu vas donner plus ou moins de travail au navigateur.

Regarde sur MDN tu trouveras la liste de toutes les propriétés "animatable".

C'est d'ailleurs intéréssant de voir que certaines propriétés comme z-index ou order peuvent être animées.

Avec de l'expérience tu arriveras à détourner les choses pour animer autant que possible les proprités transform et opacity, pour cela, voyons ensemble un autre exemple, celui de l'animation sur le déplacement d'un élément.

Animer le déplacement d'un élément de façon optimisée

Pour déplacer un élément, tu peux animer ses marges internes ou externes (margin et padding), mais c'est fortement déconseillé car tu fais intervenir des changements sur la position de l'élément (étape layout), et surtout tu risques de causer un déplacement des autres éléments de ta page, créant une réaction en chaîne.

Si ton élément est en position absolue, on évitera d'animer top, right, bottom ou left car elles ne sont pas optimisées pour les animations, ces propriétés provoquent aussi un recalcul du positionnement de l'élément.

Prenons le code HTML suivant :

<div class="box-parent">
  <div class="box"></div>
</div>

Puis on ajoute une animation sur la propriété left sur le déplacement de l'élément au hover :

.box-parent {
  position: relative;
}
.box {
  position: absolute;
  left: 0;
  transition: left 5s ease-in-out;
}
.box-parent:hover .box {
  left: 200px;
  /* On aurait aussi pu mettre margin-left: 200px; */
}

La même animation, mais on préferera ici animer avec la propriété transform :

.box {
  transition: transform 5s ease-in-out;
}
.box-parent:hover .box {
  transform: translateX(200px);
}
🐌
🚀

La différence sur cette animation est potentiellement visible à l'œil, sur ma machine l'animation sur la propriété margin-left paraît moins fluide que celle sur la propriété transform. C'est peut-être le cas aussi chez toi ? Mais j'en conviens la différence n'est pas flagrante, voire pas visible du tout.

Tu n'es peut-être pas convaincu mais laisse moi te montrer un autre exemple. On va reproduire la même animation, mais cette fois on va faire aussi descendre le carré de quelques pixels à l'aide de la propriété top, et dans le cas de l'animation optimisée, on utilisera la fonction translateY().

Le CSS pour l'animation sur les propriétés left et top :

.box {
  transition: left 5s ease-in-out, top 5s ease-in-out;
}
.box-parent:hover .box {
  left: 200px;
  top: 50px;
}

Le CSS pour l'animation sur la propriété transform :

.box {
  transition: transform 5s ease-in-out;
}
.box-parent:hover .box {
  transform: translateX(200px) translateY(50px);
}
🐌
🚀

Sur les deux animations, le carré se déplace de 50px vers le bas, mais si tu regardes bien l'animation sur le top et le left, tu verras des saccades qui correspondent à la position actualisée du carré. Une sorte d'effet escalier qui n'est pas très esthétique.

Pourquoi a t-on ce résultat ? Car les propriétés top et left provoquent un nouveau rendu du layout, elles forcent le navigateur à recaculer les positions et dimensions de l'élément animé.

Il n'y a pas cet effet sur l'animation avec transform car le navigateur est capable ici d'aller "entre les pixels" en quelque sorte.

Voir les impacts des animations sur les performances

Le graphique en début d'article est une représentation de ce que tu peux voir dans le volet "Surveillance des performances" ou "Performances monitor" de Google Chrome.

Pour y accéder, tu dois te rendre dans les outils de développement de Chrome en faisant un Ctrl/Cmd + Maj + I, aller dans le menu "Plus d'outils" et "Surveillance des performances".

Tu auras alors ce panneau d'ouvert :

'Comparaison entre les recalculs de styles du navigateur lors d'animations sur les propriétés box-shadow et opacity'

Dans ce panneau tu peux voir entre autres :

  • l'utilisation de l'UC (CPU)
  • les calques par seconde (le layout shifting)
  • les recalculs de styles / seconde

J'ai également mis en surbrillance deux options qui peuvent te permettre d'émuler un processeur et une connexion plus lente, cela permet de voir encore mieux que même une simple animation CSS peut avoir des impacts importants sur les performances.

Une petite vidéo pour voir en direct les impacts lorsque les animations sont déclenchées :

Si on compare l'animation de l'ombre portée, sur les propriétés opacity et box-shadow, voici ce qu'on peut retenir :

  • les deux animations provoquent des recalculs de styles, on change leur apparence donc c'est normal, mais il y a en plus pour l'animation sur box-shadow
  • les animations ne provoquent pas de changements au niveau du layout, ce qui est une bonne chose
  • le CPU est plus sollicité lors de l'animation sur la propriété box-shadow

Si on compare l'animation du déplacement d'un élément, sur les propriétés left et transform, voici ce qu'on peut retenir :

  • ici aussi les deux animations provoquent des recalculs de styles, mais il y a bien plus pour l'animation sur left
  • seule l'animation sur la propriété left provoquent des recalculs de layouts
  • le CPU est plus sollicité lors de l'animation sur la propriété left

Le CPU n'est que très peu sollicité lorsqu'on fait des animations sur les propriétés opacity et transform car c'est le GPU qui prend le relai.

Je le répète, même si pour toi il n'y a pas de différences visuelles, ce n'est pas le cas pour les utilisateurs qui ont des machines moins puissante..

Pour conclure..

Donc on évite au maximum de faire de déclencher l'étape de Layout.

Très souvent tes animations vont causer du "repainting", c'est le cas de beaucoup d'animations comme les changements de couleurs.

Il faut noter aussi que certaines propriétés ont un impact plus important que d'autres à l'étape du painting, box-shadow et background-color vont déclencher du repainting, mais les effets de flou sont plus lourd. Pour le navigateur, peindre une ombre lui demande plus de ressources qu'une simple couleur de fond.

Si on devait résumer très simplement les choses, on pourrait dire que tu donnes plus ou moins de travail au navigateur en fonction des propriétés que tu animes !

Si le sujet des performances Web t'intéresses : j'ai écris un article sur les Core Web Vitals, je t'invite à le lire !

Je termine juste sur un point, depuis que je m'interésse au sujet des performances Web, je me suis rendu compte que tout à un coût. On est vite tenter de vouloir faire les choses les plus simple possible, en mettant moins d'effets, moins d'images etc.

C'est peut-ête une bonne chose, avoir des pages Web plus simple ? Toutes les animations ont un "coût" pour le navigateur, il y a juste des façons plus optimisées de les faire.

Je te remercie de m'avoir lu, n'hésites pas à me donner ton avis sur Twitter !

Si tu souhaites être prévenu de la sortie des prochains articles et contenu du site, tu trouveras le formulaire d'inscription à ma newsletter un peu plus bas.

À bientôt, Seb.

D'autres articles qui peuvent t'intérésser :

2024 Sébastien Imbert