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 tenons à 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, n'hésitez pas à nous faire part de vos commentaires.

Cet article aborde trois thèmes 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 à vos 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 l'ajout d'un trop grand nombre d'animations peuvent nuire à l'expérience et la rendre vraiment peu amusante. 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 préfèrent donc les mouvements réduits, 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. Paint:remplissez les pixels de chaque élément en 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.
  • Ajuster les propriétés de paint
  • 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 est effectuée tôt, plus les coûts sont importants et moins elle sera fluide. (Pour en savoir plus, consultez la section Performances de rendu.)

Bien qu'il puisse être pratique d'animer des propriétés de mise en page, cela entraîne des coûts, même si ces coûts ne sont pas immédiatement visibles. 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 des animations Web efficaces présentent 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 un certain temps à s'afficher. 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 (de 90 à 120 Hz deviennent courants). Souvent, ces écrans peuvent s'adapter activement entre les fréquences d'actualisation selon les besoins, ou même 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 images supprimées ne deviennent visibles que lorsqu'il existe 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
  • Utiliser des anti-modèles de conception qui désactivent les optimisations rapides de rendu
  • 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'occasion 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 longs blocs inactifs empêche le navigateur de planifier d'autres tâches de longue durée (telles qu'une récupération de mémoire plus longue 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 images de l'animation. Consultez l'exemple à l'abandon pour voir comment une animation pilotée par rAF (une fois qu'il y a trop de travail sur le thread principal (par exemple, la mise en page)) entraînera la perte de frames, moins de rappels rAF et une diminution du FPS.

Lorsque le thread principal est bloqué, 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 supprimés sur le thread principal, mais qui présente toujours de nombreux frames de défilement diffusés avec succès sur le thread du compositeur. Une fois la longue tâche terminée, la mise à jour principale de la peinture du thread n'a aucune modification visuelle à offrir. L'interrogation rAF suggérait une perte de frames sur 0, mais visuellement, un utilisateur ne remarquerait aucune 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 simultanément présenter un contenu non actualisé 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. Ici, le navigateur va avoir une nouvelle mise à jour, mais il ne s'agit peut-être pas de la toute dernière mise à jour.

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 rendus de 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, 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 saturé, 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, d'un point de vue technique, réduire le nombre de mises à jour de frames supprimées, cela apparaîtra visuellement comme un débit d'images 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 de l'utilisateur, sont plus claires à 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 ou une animation visuelle. Par exemple, l'utilisation de l'interrogation rAF uniquement pour suivre la fréquence d'images (comme indiqué ci-dessus) ne devrait pas elle-même 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. Ce phénomène n'est pas capturé directement par les métriques de fluidité de l'animation, mais peut encore être perturbant 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 d&#39;une école en construction

Je veux dire, je suppose qu'ils étaient plutôt cool pour le moment !

É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é validée dans le délai imparti, ou aucune mise à jour du thread principal n'était souhaitée.
Partiellement présentée 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 Compositeur uniquement. Le thread principal a eu une mise à jour visuelle qui affecte la fluidité, mais un frame précédemment obsolète est arrivé et a été utilisé à la place.
Partiellement présentée 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 une certaine note. 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 "à première vue" à 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 éléments suivants comme ensemble initial de métriques:

  • Pourcentage moyen d'abandon:pour toutes les images d'animation non inactives sur l'ensemble de la timeline
  • 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 par vous-même dans les outils pour les développeurs.

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

Visionneuse de frames dans les enregistrements de profils de performances des outils de développement

Le panneau Performances des outils de développement dispose depuis longtemps d'une visionneuse 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.

Aujourd'hui, vous constaterez que les cadres dans la visionneuse de cadres sont mieux alignés sur les limites de vsync et sont codés par couleur en fonction de l'état. Il n'existe toujours pas de visualisation 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 le traçage Chrome, 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 des fonctionnalités disponibles dans le document Life of a frame (Vie d'un cadre).

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, découvrez le temps nécessaire pour obtenir 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 des expériences utilisateur de qualité 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é des animations.

Nous vous tiendrons informé à mesure que nous poursuivrons nos recherches 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 permettant de mesurer la fluidité des animations de manière performante pour des utilisateurs réels sur le terrain ainsi que lors de l'atelier. Nous vous communiquerons prochainement plus d'informations à ce sujet.

Commentaires

Nous sommes ravis de toutes les améliorations récentes et de tous les outils pour les développeurs fournis dans Chrome pour mesurer la fluidité des animations. Essayez ces outils, analysez vos animations et dites-nous où cela mène.

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 !