Vers une métrique de fluidité de l'animation

Découvrez comment mesurer les animations, comment penser aux images d'animation et comment assurer la fluidité globale de la page.

Behdad Bakhshinategh
Behdad Bakhshinategh
Jonathan Ross
Jonathan Ross
Michal Mocny
Michal Mocny

Vous avez probablement déjà rencontré des pages qui "saccadent" ou "se figent" lors du défilement ou des animations. Nous aimons dire que ces expériences ne sont pas fluides. Pour résoudre ces types de problèmes, l'équipe Chrome a travaillé sur l'ajout de plus de compatibilité à nos outils de laboratoire pour la détection d'animation, ainsi que sur l'amélioration constante des diagnostics du pipeline de rendu dans Chromium.

Nous souhaitons partager quelques progrès récents, vous donner des conseils concrets sur les outils et discuter d'idées pour de futures métriques de fluidité d'animation. Comme toujours, nous aimerions connaître votre avis.

Cet article aborde trois sujets principaux:

  • Présentation rapide des animations et des frames d'animation.
  • Nos réflexions actuelles sur la mesure de la fluidité globale de l'animation.
  • Voici quelques suggestions pratiques que vous pouvez utiliser dans les outils de laboratoire dès aujourd'hui.

Que sont les animations ?

Les animations donnent vie aux contenus. En faisant bouger le contenu, en particulier en réponse aux interactions des utilisateurs, les animations peuvent rendre une expérience plus naturelle, plus compréhensible et plus amusante.

Toutefois, des animations mal implémentées ou simplement trop nombreuses peuvent dégrader l'expérience et la rendre tout à fait déplaisante. Nous avons probablement tous déjà interagi avec une interface qui a ajouté trop d'effets de transition "utiles", qui deviennent en réalité hostiles à l'expérience lorsqu'ils ne fonctionnent pas bien. Certains utilisateurs peuvent donc préférer un mouvement réduit, une préférence que vous devez respecter.

Comment fonctionnent les animations ?

Pour rappel, le pipeline de rendu se compose de quelques étapes séquentielles:

  1. Style:calcule les styles qui s'appliquent aux éléments.
  2. Mise en page:génère la géométrie et la position de chaque élément.
  3. Peinture:remplissez les pixels de chaque élément dans des calques.
  4. Composite:dessine les calques à l'écran.

Bien qu'il existe de nombreuses façons de définir des animations, elles fonctionnent toutes fondamentalement via l'une des méthodes suivantes:

  • Ajustement des propriétés de mise en page.
  • Ajustement des propriétés de la peinture.
  • Ajustement des propriétés composites.

Étant donné que ces étapes sont séquentielles, il est important de définir les animations en termes de propriétés 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 est fluide. (Pour en savoir plus, consultez la section 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 ces coûts ne sont pas immédiatement apparents. 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 des animations Web, et vous assurer d'animer des propriétés composites, est un excellent premier pas pour garantir des animations fluides et efficaces. Toutefois, cela ne garantit pas 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 ?

Les modifications apportées à la représentation visuelle d'une page mettent du temps à apparaître. Un changement visuel entraîne un nouveau frame d'animation, qui est finalement affiché sur l'écran de l'utilisateur.

L'affichage est mis à jour à un certain intervalle, de sorte que les mises à jour visuelles sont groupées. De nombreux écrans s'actualisent à un intervalle de temps fixe, par exemple 60 fois par seconde (soit 60 Hz). Certains écrans plus modernes peuvent offrir des fréquences d'actualisation plus élevées (90 Hz à 120 Hz deviennent courants). Souvent, ces écrans peuvent s'adapter activement entre les fréquences d'actualisation selon les besoins, voire proposer 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 de contenu à partir du réseau ou l'exécution efficace de tâches JavaScript.

À un moment donné, il peut devenir trop difficile de terminer toutes les mises à jour visuelles dans le délai imparti par l'écran. Dans ce cas, le navigateur place un frame. L'écran ne s'éteint pas, il se répète simplement. La même mise à jour visuelle s'affiche un peu plus longtemps : le même frame d'animation que celui présenté lors de l'opportunité de frame précédente.

Cela arrive souvent. Il n'est pas nécessairement perceptible, en particulier pour les contenus statiques ou ressemblant à des documents, ce qui est courant sur la plate-forme Web en particulier. Les frames perdus 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 images d'animation ?

Les développeurs Web peuvent avoir un impact important sur la capacité d'un navigateur à afficher et à présenter rapidement et efficacement les mises à jour visuelles.

Voici quelques exemples :

  • Utilisation de contenus trop volumineux ou gourmands en ressources pour être décodés rapidement sur l'appareil cible.
  • Utilisation de trop de calques nécessitant trop de mémoire GPU.
  • Définir des styles CSS ou des animations Web trop complexes
  • Utilisation d'anti-modèles de conception qui désactivent les optimisations de rendu rapide.
  • Trop de travail 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é une perte de frame ?

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 l'opportunité de le faire avant l'étape de peinture suivante du pipeline de rendu. Si votre fonction de rappel n'est pas appelée au moment prévu, cela signifie qu'une peinture n'a pas été exécutée et qu'un ou plusieurs frames ont été ignorés. En effectuant des requêtes 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'utilisation de la méthode de sondage 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 inactifs lorsqu'elle est utilisée en continu (blocs qui dépassent un seul frame).
  • De même, l'absence de blocs d'inactivité prolongés empêche le navigateur de planifier d'autres tâches de longue durée (telles que le nettoyage de la mémoire plus long et d'autres tâches en arrière-plan ou spéculatives).
  • Si l'interrogation est activée et désactivée, vous ne verrez pas les cas où le budget de frame a été dépassé.
  • La scrutation génère des faux positifs lorsque le navigateur utilise une fréquence de mise à jour variable (par exemple, en raison de l'état de l'alimentation ou de la visibilité).
  • Et surtout, il ne capture pas tous les types de mises à jour d'animation.

Trop de travail sur le thread principal peut affecter la possibilité de voir les frames d'animation. Consultez l'exemple de saccade pour voir comment une animation basée sur rAF, une fois qu'il y a trop de travail sur le thread principal (comme la mise en page), entraîne des images perdues, moins de rappels rAF et des FPS plus faibles.

Lorsque le thread principal est ralenti, les mises à jour visuelles commencent à s'interrompre. C'est bizarre !

De nombreux outils de mesure se sont concentrés sur la capacité du thread principal à produire des résultats dans les meilleurs délais et sur la fluidité des images 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 ruinent complètement la capacité de la page à fournir certains types de mises à jour visuelles. Vous pouvez voir dans l'angle supérieur gauche une baisse correspondante des FPS signalés par requestAnimationFrame() à 0.

Pourtant, malgré ces tâches longues, la page continue de défiler de manière fluide. En effet, dans les navigateurs modernes, le défilement est souvent en thread, entièrement géré par le compositeur.

Il s'agit d'un exemple qui contient simultanément de nombreux frames abandonnés sur le thread principal, mais qui comporte encore de nombreux frames de défilement correctement diffusés sur le thread du compositeur. Une fois la tâche longue terminée, la mise à jour de la peinture du thread principal n'offre aucun changement visuel. Le sondage rAF suggérait une chute de frame à 0, mais visuellement, un utilisateur ne pourrait pas remarquer de différence !

Pour les images animées, ce n'est pas si simple.

Cadres d'animation: mises à jour importantes

L'exemple ci-dessus montre que l'histoire ne se limite pas à requestAnimationFrame().

Quand les mises à jour d'animation et les images d'animation sont-elles importantes ? Voici quelques critères que nous envisageons et sur lesquels nous aimerions avoir votre avis:

  • Mises à jour du thread principal et du thread du moteur de rendu
  • Mises à jour de peinture manquantes
  • Détecter les animations
  • Qualité ou quantité

Mises à jour du thread principal et du thread du moteur de rendu

Les mises à jour des frames d'animation ne sont pas booléennes. Il n'est pas nécessaire que les frames ne puissent être entièrement abandonnés ou entièrement présentés. Plusieurs raisons peuvent expliquer qu'un frame d'animation soit partiellement présenté. En d'autres termes, il peut présenter à la fois du contenu obsolète et de nouvelles mises à jour visuelles.

L'exemple le plus courant est celui où le navigateur ne parvient pas à générer une nouvelle mise à jour du thread principal avant la date limite du frame, mais qu'il dispose d'une nouvelle mise à jour du thread du compositeur (comme dans l'exemple de défilement par thread précédent).

L'une des raisons importantes pour lesquelles il est recommandé d'utiliser des animations déclaratives pour animer des propriétés composites est qu'elles permettent d'animer entièrement une animation par le thread du moteur de rendu, même lorsque le thread principal est occupé. Ces types d'animations peuvent continuer à produire des mises à jour visuelles efficacement et en parallèle.

D'autre part, il peut arriver qu'une mise à jour du thread principal devienne finalement disponible pour la présentation, mais seulement après avoir manqué plusieurs échéances de frame. Le navigateur dispose ici d'une nouvelle mise à jour, mais il ne s'agit peut-être pas de la dernière.

De manière générale, nous considérons les frames qui contiennent certaines nouvelles mises à jour visuelles, mais pas toutes, comme des frames partiels. Les frames partiels sont assez courants. Idéalement, les mises à jour partielles devraient inclure au moins les mises à jour visuelles les plus importantes, comme les animations, mais cela ne peut se produire que si les animations sont gérées par le thread du moteur de rendu.

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 terminé le décodage et la rastérisation à temps pour la présentation du frame.

Ou, même si une page est parfaitement statique, les navigateurs peuvent toujours être en retard sur le rendu des mises à jour visuelles lors d'un défilement rapide. En effet, les représentations de pixels du contenu au-delà du viewport visible peuvent être supprimées pour économiser de la mémoire GPU. L'affichage des pixels prend du temps, et il peut s'écouler plus d'un seul frame pour afficher tout après un défilement important, comme un balayage du doigt. C'est ce que l'on appelle communément le modèle en damier.

À chaque opportunité de rendu de frame, il est possible de suivre la quantité des dernières mises à jour visuelles qui ont réellement atteint l'écran. La mesure de la capacité à le faire sur de nombreux frames (ou au fil du temps) est généralement appelée débit de frame.

Si le GPU est vraiment encombré, le navigateur (ou la plate-forme) peut même commencer à limiter la fréquence à laquelle il tente des mises à jour visuelles, ce qui réduit les fréquences d'images effectives. Bien que cela puisse techniquement réduire le nombre de mises à jour de frame abandonnées, visuellement, le débit de frame sera toujours inférieur.

Cependant, tous les types de débit de trames faible ne sont pas mauvais. Si la page est principalement inactive et qu'il n'y a pas d'animations actives, une fréquence d'images faible est tout aussi attrayante visuellement qu'une fréquence d'images élevée (et peut économiser de la batterie).

Quand le débit de trames est-il important ?

Détecter les animations

Un débit de trames élevé est important, en particulier pendant les périodes d'animations importantes. Les différents types d'animation dépendent des mises à jour visuelles d'un thread spécifique (principal, compositeur ou worker). Par conséquent, sa mise à jour visuelle dépend de ce thread qui fournit sa mise à jour 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 basées sur les entrées utilisateur, sont plus faciles à définir que les animations basées sur JavaScript implémentées en tant que 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 de la sonde 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 n'est 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 un framerate constant de 60 FPS lorsque vous regardez une vidéo. Techniquement, la lecture est parfaitement fluide, mais la vidéo elle-même peut avoir un débit binaire faible ou des problèmes de mise en mémoire tampon réseau. Cela n'est pas directement capturé par les métriques de fluidité de l'animation, mais peut toujours être choquant pour l'utilisateur.

Ou encore, un jeu qui exploite <canvas> (peut-être même en utilisant des techniques telles que le canevas hors écran pour assurer un taux de rafraîchissement stable) peut techniquement être parfaitement fluide en termes de frames d'animation, tout en ne parvenant pas à charger des éléments de jeu de haute qualité dans la scène ou à présenter des artefacts de rendu.

Et bien sûr, un site peut simplement avoir des animations vraiment mauvaises 🙂

GIF de l&#39;ancienne école en cours de construction

Je pense qu'ils étaient plutôt cool pour leur époque.

États d'un seul frame d'animation

Étant donné que les images peuvent être partiellement présentées ou que des images manquantes peuvent se produire sans affecter la fluidité, nous avons commencé à considérer que chaque image avait un score de complétude ou de fluidité.

Voici le spectre des façons dont nous interprétons l'état d'un seul frame d'animation, du meilleur au pire des cas:

Aucune mise à jour souhaitée Temps d'inactivité, répétition du frame précédent.
Présentation complète 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.
Présentation partielle Compilateur uniquement ; la mise à jour du thread principal retardée n'a entraîné aucun changement visuel.
Présentation partielle Compilateur uniquement : le thread principal a subi une mise à jour visuelle, mais cette mise à jour n'incluait pas d'animation affectant la fluidité.
Présentation partielle Compilateur uniquement : le thread principal a subi une mise à jour visuelle qui affecte la fluidité, mais un frame obsolète est arrivé et a été utilisé à la place.
Présentation partielle Compilateur uniquement, sans la mise à jour principale souhaitée, et la mise à jour du compilateur comporte une animation qui affecte la fluidité.
Présentation partielle Compositor uniquement, mais la mise à jour du compositor n'inclut pas d'animation qui affecte la fluidité.
Cadre abandonné Aucune mise à jour. Aucune mise à jour du compositeur n'était souhaitée, et le principal a été retardé.
Cadre abandonné Une mise à jour du moteur de rendu était souhaitée, mais elle a été retardée.
Cadre 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 vsync.

Il est possible de transformer ces états en quelque sorte en score. Une façon d'interpréter ce score consiste peut-être à le considérer comme une probabilité d'être observable par l'utilisateur. Un seul frame perdu peut ne pas être très visible, mais une séquence de nombreux frames perdus affectant la fluidité à la suite, c'est sûr !

Synthèse: Métrique "Pourcentage de frames perdus"

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 simplement un score rapide "en un coup d'œil" à une expérience.

Étant donné que les frames peuvent être partiellement présentés et que même les mises à jour de frame complètement ignorées peuvent ne pas affecter la fluidité, nous souhaitons nous concentrer moins sur le simple comptage des frames et davantage sur l'étendue à laquelle le navigateur ne peut pas fournir des mises à jour complètes visuellement lorsque cela est important.

Le modèle mental doit passer de:

  1. Images par seconde, pour
  2. Détecter les mises à jour d'animation manquantes et importantes pour :
  3. Pourcentage de baisse sur une période donnée.

Ce qui compte, c'est la proportion du temps d'attente pour les mises à jour importantes. Nous pensons que cela correspond à la façon naturelle dont les utilisateurs perçoivent la fluidité des contenus Web. Jusqu'à présent, nous avons utilisé les métriques suivantes comme ensemble initial:

  • Pourcentage moyen de frames abandonnés:pour tous les frames d'animation non inactifs sur l'ensemble de la chronologie
  • Cas le plus défavorable du pourcentage d'images perdues:mesuré sur des périodes glissantes d'une seconde.
  • 95e centile du pourcentage d'images perdues:mesuré sur des fenêtres glissantes de 1 seconde.

Vous pouvez déjà consulter ces scores dans certains outils pour les développeurs Chrome. Bien que ces métriques ne portent que sur le débit global des images, nous évaluons également d'autres facteurs, tels que la latence des images.

Essayez-le vous-même dans les outils de développement.

HUD Performance

Chromium dispose d'un HUD de performances bien conçu, caché derrière un indicateur (chrome://flags/#show-performance-metrics-hud). Vous y trouverez des scores en temps réel pour des éléments tels que les Core Web Vitals, ainsi que quelques définitions expérimentales pour la fluidité de l'animation basée sur le pourcentage de frames perdus au fil du temps.

HUD Performance

Statistiques de rendu d'image

Activez "Frame Rendering Stats" (Statistiques de rendu des frames) dans DevTools via les paramètres de rendu pour afficher une vue en direct des nouveaux frames d'animation, qui sont codés par couleur pour différencier les mises à jour partielles des mises à jour de frame complètes. Les FPS indiqués ne concernent que les images entièrement présentées.

Statistiques de rendu des images

Lecteur de frames dans les enregistrements de profil de performances DevTools

Le panneau "Performances" des outils de développement comporte depuis longtemps un visualiseur de frames. Cependant, il était devenu un peu en décalage avec le fonctionnement réel du pipeline de rendu moderne. De nombreuses améliorations ont été apportées récemment, même dans la dernière version Canary de Chrome, ce qui, selon nous, facilitera grandement le débogage des problèmes d'animation.

Vous constaterez aujourd'hui que les images dans l'aperçu des images sont mieux alignées sur les limites de vsync et codées par couleur en fonction de leur état. La visualisation n'est pas encore complète pour toutes les nuances décrites ci-dessus, mais nous prévoyons d'en ajouter d'autres prochainement.

Visionneuse de frame dans Chrome DevTools

Traçage Chrome

Enfin, avec Chrome Tracing, l'outil de choix pour examiner en détail les détails, vous pouvez enregistrer une trace de"rendu de contenu Web" via la nouvelle UI Perfetto (ou about:tracing), et examiner en détail le pipeline graphique de Chrome. Cette tâche peut s'avérer ardue, mais quelques éléments ont récemment été ajoutés à Chromium pour la faciliter. Vous pouvez obtenir un aperçu de ce qui est disponible dans le document Vie d'un frame.

Les événements de trace vous permettent de déterminer définitivement:

  • Quelles animations sont en cours d'exécution (à l'aide d'événements nommés TrackerValidation) ?
  • Obtenir la chronologie exacte des images d'animation (à l'aide d'événements nommés PipelineReporter).
  • Pour les mises à jour d'animation saccadées, déterminez exactement ce qui empêche votre animation de s'exécuter plus rapidement (à l'aide des répartitions des événements dans les événements PipelineReporter).
  • Pour les animations basées sur les entrées, vérifiez la durée d'obtention d'une mise à jour visuelle (à l'aide d'événements nommés EventLatency).

Outil de création de rapports sur le pipeline de traçage Chrome

Étape suivante

L'initiative Web Vitals vise à fournir des métriques et des conseils pour créer une expérience utilisateur optimale sur le Web. Les métriques basées sur des tests en laboratoire, comme le temps de blocage total (TBT), sont essentielles pour détecter et diagnostiquer les problèmes d'interactivité potentiels. Nous prévoyons de concevoir une métrique basée sur un atelier similaire pour la fluidité de l'animation.

Nous vous tiendrons informés de nos avancées dans la conception d'une métrique complète basée sur les données de chaque frame d'animation.

À l'avenir, nous aimerions également concevoir des API permettant de mesurer efficacement la fluidité des animations pour les utilisateurs réels sur le terrain et en laboratoire. Nous vous communiquerons prochainement plus d'informations à ce sujet.

Commentaires

Nous sommes ravis de toutes les améliorations et outils pour les développeurs récemment intégrés à Chrome pour mesurer la fluidité des animations. Veuillez tester ces outils, comparer vos animations et nous faire part de vos résultats.

Vous pouvez envoyer vos commentaires au groupe Google web-vitals-feedback en indiquant "[Smoothness Metrics]" dans l'objet. Nous avons hâte de connaître votre avis.