Découvrez comment mesurer les animations, réfléchir aux frames d'animation et améliorer la fluidité globale des pages.
Vous avez probablement déjà rencontré des pages qui "bégayaient" ou "se figeaient" lors du défilement ou des animations. Nous disons que ces expériences ne sont pas fluides. Pour résoudre ces types de problèmes, l'équipe Chrome s'efforce d'ajouter davantage de compatibilité à nos outils de laboratoire pour la détection des animations, ainsi que d'améliorer constamment les diagnostics du pipeline de rendu dans Chromium.
Nous aimerions vous faire part de nos récents progrès, vous donner des conseils concrets sur les outils et discuter d'idées pour de futures métriques de fluidité des animations. Comme toujours, nous aimerions recevoir vos commentaires.
Cet article aborde trois thèmes principaux :
- Aperçu rapide des animations et des frames d'animation.
- Nos réflexions actuelles sur la mesure de la fluidité globale des animations.
- Voici quelques suggestions pratiques que vous pouvez utiliser dès aujourd'hui dans les outils de l'atelier.
Que sont les animations ?
Les animations donnent vie à votre contenu. En faisant bouger le contenu, en particulier en réponse aux interactions de l'utilisateur, les animations peuvent rendre une expérience plus naturelle, compréhensible et amusante.
Cependant, des animations mal implémentées ou trop nombreuses peuvent dégrader l'expérience et la rendre désagréable. Nous avons probablement tous interagi avec une interface qui a ajouté trop d'effets de transition "utiles", qui deviennent en fait hostiles à l'expérience lorsqu'ils fonctionnent mal. Par conséquent, certains utilisateurs peuvent préférer les mouvements réduits, une préférence utilisateur que vous devez respecter.
Comment fonctionnent les animations ?
Pour rappel, le pipeline de rendu se compose de plusieurs étapes séquentielles :
- Style : calculez les styles qui s'appliquent aux éléments.
- Mise en page : générez la géométrie et la position de chaque élément.
- Peinture : remplissez les pixels de chaque élément dans des calques.
- Composite : dessine les calques à l'écran.
Bien qu'il existe de nombreuses façons de définir des animations, elles fonctionnent toutes fondamentalement de l'une des manières suivantes :
- Ajustez les propriétés de mise en page.
- Ajuster les propriétés de peinture.
- Ajuster les propriétés composites.
Comme ces étapes sont séquentielles, il est important de définir les animations en termes de propriétés qui se trouvent plus loin dans le pipeline. Plus la mise à jour intervient tôt dans le processus, plus les coûts sont élevés et moins elle a de chances de se dérouler sans problème. (Pour en savoir plus, consultez Performances de rendu.)
Bien qu'il puisse être pratique d'animer les propriétés de mise en page, cela a un coût, même si celui-ci n'est pas immédiatement apparent. Dans la mesure du possible, les animations doivent être définies en termes de modifications de propriétés composites.
Définir des animations CSS déclaratives ou utiliser Web Animations et s'assurer d'animer les propriétés composites est un excellent premier pas pour garantir des animations fluides et efficaces. Mais cela ne garantit pas pour autant la fluidité, car même les animations Web efficaces ont des limites de performances. C'est pourquoi il est toujours important de mesurer !
Que sont les frames d'animation ?
La mise à jour de la représentation visuelle d'une page prend du temps. Un changement visuel entraîne une nouvelle image d'animation, qui est finalement affichée sur l'écran de l'utilisateur.
Affiche la mise à jour à un certain intervalle, de sorte que les mises à jour visuelles sont regroupées. De nombreux écrans se mettent à jour à intervalles de temps fixes, par exemple 60 fois par seconde (soit 60 Hz). Certains écrans plus récents peuvent offrir des fréquences d'actualisation plus élevées (90 à 120 Hz deviennent courants). Souvent, ces écrans peuvent s'adapter activement entre les taux de rafraîchissement selon les besoins, ou même offrir des fréquences d'images entièrement variables.
L'objectif de toute application, comme un jeu ou un navigateur, est de traiter toutes ces mises à jour visuelles groupées et de produire un frame d'animation visuellement complet dans les délais, à chaque fois. Notez que cet objectif est entièrement distinct des autres tâches importantes du navigateur, telles que le chargement rapide du contenu à partir du réseau ou l'exécution efficace des tâches JavaScript.
À un moment donné, il peut devenir trop difficile d'effectuer toutes les mises à jour visuelles dans le délai imparti par l'écran. Dans ce cas, le navigateur supprime un frame. Votre écran ne devient pas noir, il se répète simplement. La même mise à jour visuelle s'affiche un peu plus longtemps, c'est-à-dire la même frame d'animation qui a été présentée lors de la frame précédente.
Cela arrive souvent ! Il n'est pas nécessairement perceptible, en particulier pour le contenu statique ou de type document, qui est courant sur la plate-forme Web en particulier. Les frames abandonnés ne deviennent apparents que lorsqu'il y a des mises à jour visuelles importantes, telles que des animations, pour lesquelles nous avons besoin d'un flux constant de mises à jour d'animation pour afficher un mouvement fluide.
Qu'est-ce qui a un impact sur les frames d'animation ?
Les développeurs Web peuvent avoir un impact considérable sur la capacité d'un navigateur à afficher rapidement et efficacement les mises à jour visuelles.
Voici quelques exemples :
- Utilisation de contenu trop volumineux ou nécessitant trop de ressources pour être décodé rapidement sur l'appareil cible.
- Utilisation d'un trop grand nombre de couches nécessitant trop de mémoire GPU.
- Définir des styles CSS ou des animations Web trop complexes
- Utiliser des anti-patterns de conception qui désactivent les optimisations de rendu rapide.
- Trop de tâches JavaScript sur le thread principal, ce qui entraîne des tâches longues qui bloquent les mises à jour visuelles.
Mais comment savoir quand un frame d'animation a manqué son échéance et a entraîné un frame abandonné ?
Une méthode possible consiste à utiliser l'interrogation requestAnimationFrame()
, mais elle présente plusieurs inconvénients. requestAnimationFrame()
, ou "rAF", indique au navigateur que vous souhaitez effectuer une animation et demande une opportunité de le faire avant la prochaine étape de peinture du pipeline de rendu. Si votre fonction de rappel n'est pas appelée au moment prévu, cela signifie qu'un rendu n'a pas été exécuté et qu'un ou plusieurs frames ont été ignorés. En interrogeant et en comptabilisant la fréquence d'appel de rAF, vous pouvez calculer une sorte de métrique "images par seconde" (FPS).
let frameTimes = [];
function pollFramesPerSecond(now) {
frameTimes = [...frameTimes.filter(t => t > now - 1000), now];
requestAnimationFrame(pollFramesPerSecond);
console.log('Frames per second:', frameTimes.length);
}
requestAnimationFrame(pollFramesPerSecond);
L'interrogation requestAnimationFrame()
n'est pas une bonne idée pour plusieurs raisons :
- Chaque script doit configurer sa propre boucle d'interrogation.
- Il peut bloquer le chemin critique.
- Même si l'interrogation rAF est rapide, elle peut empêcher
requestIdleCallback()
de planifier de longs blocs d'inactivité en cas d'utilisation continue (blocs qui dépassent un seul frame). - De même, le manque de blocs d'inactivité de longue durée empêche le navigateur de planifier d'autres tâches de longue durée (comme la collecte des déchets de longue durée et d'autres tâches en arrière-plan ou spéculatives).
- Si l'interrogation est activée et désactivée, vous manquerez les cas où le budget de frame a été dépassé.
- La scrutation signalera des faux positifs dans les cas où le navigateur utilise une fréquence de mise à jour variable (par exemple, en raison de l'état de l'alimentation ou de la visibilité).
- Plus important encore, il ne capture pas tous les types de mises à jour d'animation.
Si le thread principal est trop sollicité, cela peut avoir un impact sur la capacité à voir les frames d'animation. Consultez l'exemple de saccade pour voir comment une animation pilotée par rAF, lorsqu'il y a trop de travail sur le thread principal (comme la mise en page), entraînera une perte d'images et moins de rappels rAF, ainsi qu'une baisse du nombre d'images par seconde.
Lorsque le thread principal est surchargé, les mises à jour visuelles commencent à saccader. C'est du jank !
De nombreux outils de mesure se sont concentrés sur la capacité du thread principal à céder la place en temps voulu et sur le bon déroulement des frames d'animation. Mais ce n'est pas tout ! Prenons l'exemple suivant :
La vidéo ci-dessus montre une page qui injecte périodiquement des tâches longues dans le thread principal. Ces tâches longues empêchent complètement la page de fournir certains types de mises à jour visuelles. Vous pouvez voir en haut à gauche une baisse correspondante de requestAnimationFrame()
FPS à 0.
Et pourtant, malgré ces longues tâches, la page continue de défiler de manière fluide. En effet, sur les navigateurs modernes, le défilement est souvent threadé et entièrement géré par le compositeur.
Il s'agit d'un exemple qui contient simultanément de nombreux frames supprimés sur le thread principal, mais qui comporte tout de même de nombreux frames de défilement correctement transmis sur le thread du compositeur. Une fois la tâche longue terminée, la mise à jour de la peinture du thread principal n'a de toute façon aucun changement visuel à offrir. Le polling rAF a suggéré une chute de frame à 0, mais visuellement, un utilisateur ne pourrait pas remarquer de différence !
Pour les frames d'animation, l'histoire n'est pas aussi simple.
Images d'animation : les mises à jour qui comptent
L'exemple ci-dessus montre qu'il y a plus à dire que simplement requestAnimationFrame()
.
Quand les mises à jour et les frames d'animation sont-ils importants ? Voici quelques critères que nous envisageons et sur lesquels nous aimerions recueillir vos commentaires :
- Mises à jour des threads principal et de compositeur
- Mises à jour de peinture manquantes
- Détecter les animations
- Qualité ou quantité
Mises à jour des threads principal et de compositeur
Les mises à jour des frames d'animation ne sont pas booléennes. Il n'est pas vrai que les frames ne peuvent être que complètement supprimées ou complètement présentées. Plusieurs raisons peuvent expliquer qu'un frame d'animation soit partiellement présenté. En d'autres termes, il peut simultanément contenir des contenus obsolètes et de nouvelles mises à jour visuelles.
L'exemple le plus courant est celui où le navigateur n'est pas en mesure de produire une nouvelle mise à jour du thread principal dans le délai imparti, mais dispose d'une nouvelle mise à jour du thread du compositeur (comme l'exemple de défilement avec thread vu précédemment).
L'une des raisons importantes pour lesquelles il est recommandé d'utiliser des animations déclaratives pour animer des propriétés composites est que cela permet à une animation d'être entièrement pilotée par le thread du compositeur, même lorsque le thread principal est occupé. Ces types d'animations peuvent continuer à produire des mises à jour visuelles de manière efficace et en parallèle.
D'un autre côté, il peut arriver qu'une mise à jour du thread principal soit enfin disponible pour la présentation, mais seulement après avoir manqué plusieurs délais de frames. Le navigateur aura une nouvelle mise à jour, mais il ne s'agira peut-être pas de la toute dernière.
De manière générale, nous considérons les frames qui contiennent de nouvelles mises à jour visuelles, mais pas toutes, comme des frames partielles. Les frames partiels sont assez courants. Idéalement, les mises à jour partielles incluraient au moins les mises à jour visuelles les plus importantes, comme les animations, mais cela n'est possible que si les animations sont gérées par le thread du compositeur.
Mises à jour de peinture manquantes
Un autre type de mise à jour partielle se produit lorsque des éléments multimédias tels que des images n'ont pas fini d'être décodés et rastérisés à temps pour la présentation du frame.
Même si une page est parfaitement statique, les navigateurs peuvent toujours avoir du mal à afficher les mises à jour visuelles lors d'un défilement rapide. En effet, les rendus en pixels du contenu au-delà de la fenêtre d'affichage visible peuvent être supprimés pour économiser de la mémoire GPU. L'affichage des pixels prend du temps. Il peut même prendre plus d'un frame pour tout afficher après un grand défilement, comme un balayage du doigt. C'est ce qu'on appelle communément l'effet damier.
À chaque opportunité de rendu d'image, il est possible de suivre la quantité de mises à jour visuelles les plus récentes qui sont effectivement affichées à l'écran. La mesure de cette capacité sur plusieurs frames (ou au fil du temps) est généralement appelée débit d'images.
Si le GPU est vraiment surchargé, le navigateur (ou la plate-forme) peut même commencer à limiter le taux auquel il tente de mettre à jour les éléments visuels, ce qui réduit les fréquences d'images effectives. Bien que cela puisse techniquement réduire le nombre de mises à jour d'images abandonnées, la fréquence d'images semblera toujours plus faible.
Cependant, tous les types de faible débit d'images ne sont pas mauvais. Si la page est principalement inactive et qu'il n'y a pas d'animations actives, une faible fréquence d'images est tout aussi attrayante visuellement qu'une fréquence d'images élevée (et elle peut économiser la batterie !).
Quand le débit d'images est-il important ?
Détecter les animations
Un débit d'images élevé est particulièrement important pendant les périodes avec des animations importantes. Différents types d'animations dépendent des mises à jour visuelles d'un thread spécifique (principal, compositeur ou worker). Par conséquent, la mise à jour visuelle dépend de la mise à jour de ce thread dans les délais. Nous disons qu'un thread donné affecte la fluidité chaque fois qu'une animation active dépend de la mise à jour de ce thread.
Certains types d'animations sont plus faciles à définir et à détecter que d'autres. Les animations déclaratives, ou animations déclenchées par l'entrée utilisateur, sont plus claires à définir que les animations déclenchées par JavaScript et implémentées sous forme de mises à jour périodiques des propriétés de style animables.
Même avec requestAnimationFrame()
, vous ne pouvez pas toujours supposer que chaque appel rAF produit nécessairement une mise à jour visuelle ou une animation. Par exemple, l'utilisation du polling rAF uniquement pour suivre la fréquence d'images (comme indiqué ci-dessus) ne devrait pas affecter les mesures de fluidité, car il n'y a pas de mise à jour visuelle.
Qualité ou quantité
Enfin, la détection des animations et des mises à jour des frames d'animation ne représente qu'une partie de l'histoire, car elle ne capture que la quantité de mises à jour d'animation, et non la qualité.
Par exemple, vous pouvez voir une fréquence d'images stable de 60 fps lorsque vous regardez une vidéo. Techniquement, la vidéo est parfaitement fluide, mais elle peut avoir un faible débit binaire ou des problèmes de mise en mémoire tampon du réseau. Cela n'est pas directement capturé par les métriques de fluidité de l'animation, mais peut tout de même être désagréable pour l'utilisateur.
De même, un jeu qui utilise <canvas>
(peut-être même en utilisant des techniques comme offscreen
canvas pour assurer une fréquence d'images stable) peut techniquement être parfaitement fluide en termes d'images d'animation, tout en échouant à charger des éléments de jeu de haute qualité dans la scène ou en présentant des artefacts de rendu.
Et bien sûr, un site peut simplement avoir de très mauvaises animations 🙂
Je suppose qu'ils étaient plutôt cool pour leur époque !
États d'un frame d'animation unique
Étant donné que les frames peuvent être partiellement présentés ou que des frames peuvent être supprimés de manière à ne pas affecter la fluidité, nous avons commencé à considérer que chaque frame avait un score de complétude ou de fluidité.
Voici le spectre des façons dont nous interprétons l'état d'une seule image d'animation, du meilleur au pire des cas :
Aucune mise à jour souhaitée | Temps d'inactivité, répétition de la frame précédente. |
Entièrement présenté | La mise à jour du thread principal a été effectuée dans les délais ou aucune mise à jour du thread principal n'était souhaitée. |
Partiellement présenté | Compositor uniquement ; la mise à jour différée du thread principal n'a entraîné aucune modification visuelle. |
Partiellement présenté | Compositor uniquement ; le thread principal a fait l'objet d'une mise à jour visuelle, mais celle-ci n'incluait pas d'animation affectant la fluidité. |
Partiellement présenté | Composant uniquement ; le thread principal a subi une mise à jour visuelle qui affecte la fluidité, mais un ancien frame est arrivé et a été utilisé à la place. |
Partiellement présenté | Compositor uniquement ; sans la mise à jour principale souhaitée, et la mise à jour du compositor comporte une animation qui affecte la fluidité. |
Partiellement présenté | Compositor uniquement, mais la mise à jour du compositeur ne comporte pas d'animation qui affecte la fluidité. |
Frame supprimé | Aucune mise à jour. Aucune mise à jour du compositeur n'était souhaitée, et le processus principal a été retardé. |
Frame supprimé | Une mise à jour du compositeur était souhaitée, mais elle a été retardée. |
Frame obsolète | Une mise à jour était souhaitée, elle a été produite par le moteur de rendu, mais le GPU ne l'a toujours pas présentée avant la date limite de synchronisation verticale. |
Il est possible de transformer ces états en une sorte de score. Une façon d'interpréter ce score est peut-être de le considérer comme une probabilité d'être observable par l'utilisateur. Une seule image abandonnée n'est peut-être pas très visible, mais une séquence de nombreuses images abandonnées qui affectent la fluidité, si !
Synthèse : métrique "Pourcentage de frames abandonnés"
Bien qu'il soit parfois nécessaire d'examiner en détail l'état de chaque frame d'animation, il est également utile d'attribuer une note rapide à une expérience.
Étant donné que les frames peuvent être partiellement présentés et que même les mises à jour de frames entièrement ignorées peuvent ne pas affecter la fluidité, nous souhaitons moins nous concentrer sur le simple comptage des frames et davantage sur la mesure dans laquelle le navigateur n'est pas en mesure de fournir des mises à jour visuellement complètes lorsque cela est important.
Le modèle mental doit passer de :
- Images par seconde, à
- Détecter les mises à jour d'animation manquantes et importantes, pour
- Pourcentage de perte sur une période donnée.
Ce qui compte, c'est la proportion de temps d'attente pour les mises à jour importantes. Nous pensons que cela correspond à la façon naturelle dont les utilisateurs perçoivent la fluidité du contenu Web dans la pratique. Jusqu'à présent, nous avons utilisé les métriques suivantes comme ensemble initial :
- Pourcentage moyen de frames supprimées : pour toutes les frames d'animation non inactives tout au long de la timeline
- Pire cas de pourcentage d'images perdues : mesuré sur des périodes de temps glissantes d'une seconde.
- 95e centile du pourcentage d'images abandonnées : mesuré sur des fenêtres glissantes d'une seconde.
Vous pouvez trouver ces scores dans certains outils pour les développeurs Chrome. Bien que ces métriques ne se concentrent que sur le débit d'images global, nous évaluons également d'autres facteurs, tels que la latence des images.
Essayez-le vous-même dans les outils pour les développeurs !
HUD des performances
Chromium dispose d'un HUD de performances bien pratique, caché derrière un indicateur (chrome://flags/#show-performance-metrics-hud
). Vous y trouverez des scores en direct pour des éléments tels que les Core Web Vitals, ainsi que quelques définitions expérimentales pour la fluidité des animations basées sur le pourcentage de frames abandonnées au fil du temps.
Statistiques de rendu d'image
Activez "Statistiques de rendu des frames" dans les outils de développement via les paramètres de rendu pour afficher en direct les nouvelles frames d'animation, qui sont codées par couleur pour différencier les mises à jour partielles des mises à jour de frames entièrement supprimées. Les FPS indiqués ne concernent que les frames entièrement présentés.
Lecteur de frames dans les enregistrements de profil de performances des outils de développement
Le panneau "Performances" des outils de développement comporte depuis longtemps un outil de visualisation des frames. Cependant, il était devenu un peu désynchronisé par rapport au fonctionnement réel du pipeline de rendu moderne. De nombreuses améliorations ont été apportées récemment, y compris dans la dernière version de Chrome Canary, qui, selon nous, facilitera grandement le débogage des problèmes d'animation.
Aujourd'hui, vous constaterez que les frames du visualiseur de frames sont mieux alignés sur les limites de vsync et codés par couleur en fonction de leur état. Nous ne disposons pas encore d'une visualisation complète pour toutes les nuances décrites ci-dessus, mais nous prévoyons d'en ajouter d'autres prochainement.
Traçage Chrome
Enfin, avec Chrome Tracing, l'outil de choix pour examiner les détails en profondeur, vous pouvez enregistrer une trace de"Rendu du contenu Web" via la nouvelle UI Perfetto (ou about:tracing
) et examiner en détail le pipeline graphique de Chrome. Cette tâche peut sembler intimidante, mais quelques éléments ont récemment été ajoutés à Chromium pour la faciliter. Pour obtenir un aperçu de ce qui est disponible, consultez le document Cycle de vie d'un frame.
Les événements de trace vous permettent de déterminer précisément :
- Animations en cours d'exécution (à l'aide d'événements nommés
TrackerValidation
). - Obtenir la chronologie exacte des frames d'animation (à l'aide d'événements nommés
PipelineReporter
). - Pour les mises à jour d'animations saccadées, déterminez exactement ce qui empêche votre animation de s'exécuter plus rapidement (à l'aide des répartitions d'événements dans les événements
PipelineReporter
). - Pour les animations pilotées par les entrées, voyez combien de temps il faut pour obtenir une mise à jour visuelle (à l'aide d'événements nommés
EventLatency
).
Étape suivante
L'initiative Web Vitals vise à fournir des métriques et des conseils pour créer d'excellentes expériences utilisateur sur le Web. Les métriques basées sur des tests en laboratoire, comme le temps total de blocage (TBT), sont essentielles pour détecter et diagnostiquer les problèmes d'interactivité potentiels. Nous prévoyons de concevoir une métrique similaire basée sur des tests en laboratoire pour la fluidité des animations.
Nous vous tiendrons informé à mesure que nous trouverons des idées pour concevoir une métrique complète basée sur les données de chaque frame d'animation.
À l'avenir, nous aimerions également concevoir des API qui permettent de mesurer la fluidité des animations de manière performante pour les utilisateurs réels sur le terrain ainsi qu'en laboratoire. Nous vous communiquerons également des informations à ce sujet.
Commentaires
Nous sommes ravis de toutes les améliorations et de tous les outils pour les développeurs récemment déployés dans Chrome pour mesurer la fluidité des animations. Veuillez tester ces outils, évaluer vos animations et nous dire ce que vous en pensez.
Vous pouvez envoyer vos commentaires au groupe Google web-vitals-feedback en ajoutant "[Métriques de fluidité]" dans l'objet. Nous avons hâte de connaître votre avis !