Communitygithub.com

Performance d'Animation

Gardez les animations web dans le budget de trame — animez uniquement les propriétés de composition, maintenez le mouvement hors du thread principal lorsque la page est occupée, et trouvez la cause lorsqu'une animation perd des trames.

Qu'est-ce que Performance d'Animation ?

Ce diagnostic d'animation se concentre sur une seule chose : garantir que chaque trame s'affiche dans le budget de temps imparti, en se basant sur la philosophie d'ingénierie de conception d'Emil Kowalski. Il aide les développeurs à diagnostiquer pourquoi une animation saccade, hoquette ou perd des trames, en commençant par vérifier les propriétés animées (transform/opacity versus layout/paint), l'emplacement d'exécution (thread principal versus accéléré matériel), et les pièges spécifiques comme les variables CSS héritées, le flou coûteux, et les re-rendus React. La compétence fournit une table des symptômes pour un diagnostic rapide, des règles strictes (comme ne jamais utiliser `transition: all` et ne pas conclure « c'est bon » sur une machine rapide), et un flux de travail en trois phases pour trouver, diagnostiquer et signaler la cause première. Elle couvre les plateformes web (navigateurs) et les frameworks d'animation comme CSS, Web Animations API, Motion (Framer Motion), et requestAnimationFrame, avec des conseils spécifiques pour React et les animations gestuelles.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/CxElegy/Wishlist/tree/HEAD/.claude/skills/animation-performance

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Que fait Performance d'Animation ?

Réponse Initiale

Lorsque cette compétence est invoquée pour la première fois sans question spécifique, répondez uniquement par :

Je suis prêt à découvrir pourquoi votre animation perd des trames et à la rendre fluide, mes connaissances proviennent de la philosophie d'ingénierie de conception d'Emil Kowalski.

Ne fournissez aucune autre information tant que l'utilisateur n'a pas posé une question.

Une compétence de diagnostic. Elle fait UNE chose : faire en sorte que le mouvement s'affiche à chaque trame. Elle ne choisit pas l'easing ou la durée (c'est le rôle d'animate), ne juge pas si le mouvement devrait exister (c'est le rôle de review-animations), et ne gère pas les threads React Native (c'est le rôle d'animate-expo). La question ici n'est jamais « est-ce que ça a l'air correct ? » mais « est-ce que chaque trame se termine à temps ? ».

Pourquoi C'est Important

Imaginez Sonner fonctionnant à 30fps. Même easing, mêmes durées, même code. Personne ne s'en serait soucié. La performance n'est pas une couche de polish appliquée après que le mouvement est bon ; c'est ce qui permet à un bon mouvement d'être perçu.

Posture Opérationnelle

Vous êtes un ingénieur de conception qui a déployé des panneaux, des toasts et des tableaux de bord sur de vrais appareils et a rencontré chacun des pièges ci-dessous en production. Vous savez que le MacBook du développeur est l'appareil le moins représentatif sur lequel l'animation fonctionnera jamais.

Deux modes d'échec, et le premier est le pire :

  1. « C'est fluide sur ma machine. » Un ordinateur portable rapide cache presque tous les problèmes de cette compétence. La barre est un téléphone de milieu de gamme, et sur un écran 120Hz, le budget est la moitié de ce que vous pensez.
  2. Ajouter will-change et considérer que c'est réglé. C'est la dernière étape, pas la première. La plupart des saccades sont dues à une mauvaise propriété ou à un thread principal occupé, et will-change ne résout ni l'un ni l'autre.

Règles Strictes

  1. transform et opacity par défaut. Tout le reste a besoin d'une raison. Si une propriété moins coûteuse produit le même visuel, utilisez-la, même si la propriété coûteuse « semble correcte ».
  2. Nommez la cause avant de modifier le code. Chaque correctif que vous proposez nomme laquelle des causes ci-dessous il adresse. « Ajouté will-change, semble plus fluide » n'est pas un diagnostic.
  3. Ne concluez jamais « c'est bon » à partir d'une machine rapide. Dites quels résultats vous avez vérifiés à partir du code et lesquels nécessitent un enregistrement sur un véritable appareil.
  4. Jamais de transition: all. Listez les propriétés exactes. all anime tout ce qui change ensuite, y compris les propriétés de layout que personne n'avait l'intention d'animer.
  5. Ne sacrifiez pas la correction pour une optimisation de meilleur effort. L'accélération matérielle est quelque chose que le navigateur peut accorder. Les propriétés que vous animez sont quelque chose que vous contrôlez. Investissez vos efforts dans le second.

La Table des Symptômes

Commencez ici. Faites correspondre ce que l'utilisateur voit, puis lisez la section correspondante.

ProblèmeSolution
L'animation saccadeAnimez transform/opacity, pas width/height/top/margin
L'animation d'ombre est saccadéefilter: drop-shadow()
Saccades uniquement pendant la navigation ou le chargementDéplacez l'animation vers CSS ou WAAPI pour qu'elle s'exécute hors du thread principal
x/y de Motion perd des tramesMettez à niveau vers motion 14.2+, ou animez la chaîne transform complète
L'animation faite main perd des trames dans ReactÉcrivez sur ref.current.style, pas sur l'état
Le glissement devient plus lent à mesure que la liste granditArrêtez d'animer une variable CSS héritée ; définissez transform directement
La transition de flou lag (surtout Safari)Gardez le blur() animé en dessous de 20px
Des propriétés inattendues s'animentRemplacez transition: all par des propriétés explicites
L'élément se décale de 1px au début ou à la fin du mouvementwill-change: transform, seulement une fois que vous le voyez

Le Budget de Trame

60fps est ce que le cerveau lit comme un mouvement fluide et correspond à la plupart des taux de rafraîchissement. Cela donne au navigateur 16.7ms (1000/60) par trame. Sur un écran 120Hz, maintenant la plupart des téléphones et de nombreux ordinateurs portables, c'est 8ms. Concevez pour le nombre le plus petit.

Pour l'échelle : un recalcul de layout sur une page complexe peut prendre plus de 100ms. Ce n'est pas une trame manquée, c'est une douzaine.

Deux choses décident si vous restez dans le budget :

  1. Ce que vous animez : combien de travail chaque trame coûte.
  2. Où ça s'exécute : si ce travail peut être bloqué par tout le reste que la page fait.

Ce Que Vous Animez

Pour montrer un changement visuel, le navigateur exécute jusqu'à trois étapes :

  • Layout : calcule la taille et la position de chaque élément.
  • Paint : dessine la page en couches.
  • Composite : place ces couches à l'écran.

Chaque propriété entre dans ce pipeline à l'un des trois points, et le coût diffère par un ordre de grandeur :

NiveauPropriétésCoût
Composite uniquementtransform, opacity, et dans Chrome/Firefox actuels aussi filter, clip-path, background-colorLe moins cher. Les navigateurs promeuvent ces propriétés dans leur propre couche.
Paint + compositebox-shadow, border-radius, colorPas de re-mesure, mais un redessin à chaque trame.
Layout + paint + compositewidth, height, padding, margin, top, left, border-widthLe plus cher. Un seul élément qui grandit peut déplacer tout ce qui l'entoure.

transform et opacity changent l'apparence d'un élément, pas sa position dans le flux du document, donc rien n'a besoin d'être re-mesuré. Tout le reste dans le niveau composite nécessite des tests sur différents navigateurs et appareils ; la prise en charge est inégale.

Descendre d'un niveau :

Au lieu deAnimez
width / height / padding pour grandir ou rétrécirscale()
top / left / margin pour déplacertranslate() (les pourcentages sont relatifs à la taille propre de l'élément)
Les changements display / visibilityopacity
box-shadowfilter: drop-shadow(…)

Le dernier est celui que les gens oublient. Une ombre animée semble inoffensive mais redessine l'élément à chaque trame.

Deux conséquences :

  • Les animations de layout ne font pas toujours perdre des trames. Un élément positionné de manière absolue, ou avec peu d'enfants, peut animer padding sans coût visible. Mais une animation de padding et une animation de scale() se ressemblent, et une seule d'entre elles ne peut pas régresser sur un appareil plus lent. Choisissez celle qui n'a aucun inconvénient.
  • Les propriétés de layout déplacent aussi les mauvaises choses. Animer la margin-left d'une barre latérale pousse le contenu à côté à chaque trame ; translateX ne le fait pas. Pour un tableau de bord à côté d'une barre latérale qui s'ouvre, c'est toute la page qui est re-layoutée 60 fois par seconde.

Où Ça S'exécute

JavaScript s'exécute toujours sur le thread principal. Une animation pilotée par requestAnimationFrame entre en compétition avec tout le reste que le navigateur fait. Lorsque le thread principal est occupé, le code de l'animation ne peut pas s'exécuter et la trame est en retard.

PiloteThread
JS + requestAnimationFrame (GSAP, animations de layout Motion)Thread principal, toujours
API Web Animations (element.animate(), animate() de Motion et composants motion/react)Peut être accéléré matériellement
Transitions et keyframes CSSPeut être accéléré matériellement

L'accélération matérielle signifie que l'animation s'exécute hors du thread principal, donc elle reste fluide peu importe ce que JavaScript fait. « JS » et « thread principal » ne sont pas des synonymes : une bibliothèque JS qui délègue à WAAPI peut être accélérée, une boucle rAF ne le peut pas.

Accéléré de manière fiable : transform et opacity. En cours de prise en charge : filter, clip-path, background-color.

Le modèle à surveiller est le mouvement qui s'exécute pendant que la page fait un travail lourd : transitions de page, indicateurs d'onglet pendant la navigation, tout ce qui se déclenche en même temps que le chargement de données ou l'hydratation. Le tableau de bord de Vercel animait son indicateur d'onglet actif avec une animation de layout partagée de Framer Motion. Le navigateur chargeait la page suivante exactement à ce moment-là, donc il perdait des trames. Le déplacer vers une animation CSS a résolu le problème.

Motion délègue opacity, transform, clip-path et filter à WAAPI, donc ils peuvent être accélérés. Les raccourcis x, y, scale, rotate dépendent de la version :

<motion.div animate={{ x: 100 }} />                          // motion 14.2+ : accéléré matériellement. Avant : thread principal, peut saccader
<motion.div animate={{ transform: "translateX(100px)" }} />  // accéléré matériellement dans toutes les versions

À partir de motion 14.2, les raccourcis sur un élément HTML sont combinés en une seule animation transform accélérée, donc gardez-les. Sur les versions plus anciennes, passez à la forme chaîne lorsque l'animation doit survivre à un thread principal occupé. Dans toutes les versions, ces éléments restent sur le thread principal : les animations de layout (layout, layoutId), onUpdate, transformTemplate, l'inertie, et les valeurs pilotées à chaque trame par une valeur de mouvement (glissement, useTransform).

L'accélération est une amélioration progressive. Les navigateurs la refusent silencieusement dans des conditions qui changent entre les versions ; Chrome, par exemple, n'accélérait historiquement pas les transformations basées sur des pourcentages. Ce n'est pas une raison pour arrêter d'utiliser translateY(100%). Les pourcentages sont le bon choix pour les éléments de taille inconnue (c'est ainsi que Sonner et Vaul positionnent les toasts et les panneaux), et la correction prime sur une optimisation de meilleur effort.

React : N'animez Pas via l'État

Les bibliothèques d'animation animent en dehors du cycle de rendu de React, donc elles vont bien. Le piège est une animation faite main qui appelle setState à chaque trame : le composant se re-rend toutes les 16.7ms et le rendu à lui seul dépasse le budget.

// Perd des trames : un re-rendu par trame
setY(nextY);

// Fluide : pas de re-rendu
ref.current.style.transform = `translateY(${nextY}px)`;
// ou avec une valeur de mouvement : y.set(nextY)

Recherchez les mises à jour d'état à l'intérieur de requestAnimationFrame, pointermove, scroll, ou les gestionnaires de glissement.

Deux Pièges Qui Semblent Corrects

Ces deux cas utilisent transform et perdent pourtant des trames.

Variables CSS héritées. Le glissement de Vaul devenait lent une fois qu'un panneau contenait plus d'environ 20 éléments, sans aucun re-rendu impliqué. La distance de glissement était écrite dans une variable CSS que translateY() lisait :

// Recalcule les styles pour chaque descendant, à chaque trame
const style = { "--swipe-amount": `${draggedDistance}px` };

// Ne touche que cet élément
const style = { transform: `translateY(${draggedDistance}px)` };

Les variables CSS héritent, donc en changer une sur un parent déclenche un recalcul de style pour chaque enfant. Plus il y a de contenu, plus chaque trame est lente. Soupçonnez cela chaque fois qu'une animation transform se dégrade à mesure que le contenu grandit.

Flou. filter: blur() devient rapidement coûteux, surtout dans Safari. Gardez le flou animé en dessous de 20px ; 2–5px suffisent pour adoucir un fondu enchaîné.

will-change

Un navigateur peut remettre une animation du CPU au GPU en cours de route, et cette remise peut faire décaler l'élément d'un pixel au début ou à la fin. will-change promeut l'élément à l'avance pour qu'aucune remise n'ait lieu :

.drawer {
  will-change: transform;
}

Ajoutez-le seulement une fois que vous voyez le décalage ou une perte de trames mesurée. Les navigateurs promeuvent déjà les éléments qui animent transform ou opacity, donc sur le chemin commun, il ne fait rien, et chaque couche promue coûte de la mémoire GPU. Placez-le sur l'élément qui anime, jamais sur * ou de grands conteneurs.

Là où il mérite sa place, c'est sur un paint coûteux, comme un grand élément flouté ou filtré. Promouvez-le et gardez la couche petite ; une petite couche est moins chère à composer qu'une couche plein écran.

Devriez-vous Éviter l'Animation JS ?

Non. L'animation sur le thread principal ne souffre que lorsque la page est occupée pendant son déroulement, ce qui est rare, et maintenant vous savez comment le repérer.

Framer Motion vous donne des choses que CSS ne peut pas : des ressorts réels, des animations de layout partagées, des animations de sortie pour les composants démontés, des gestes avec momentum. La question est de savoir si vous avez besoin de ces choses, pas si l'animation JS est autorisée.

La répartition qui fonctionne : CSS pour les mouvements simples et tout ce qui joue pendant que la page est occupée ; Framer Motion pour les mouvements complexes, interruptibles et pilotés par des gestes.

Flux de Travail

Phase 1 : Trouver ce qui anime

Listez chaque animation dans le périmètre avec son pilote et ses propriétés :

ÉlémentPilotePropriétésJoué pendant
Indicateur d'ongletmotion/react layoutIdlayoutchangement de route
Panneautransition CSStransformglissement utilisateur
Survol de cartetransition CSSallsurvol

Développez transition: all et les raccourcis Motion en ce qu'ils animent réellement.

Phase 2 : Diagnostiquer

Parcourez cette liste. Elle est ordonnée par la fréquence à laquelle chaque cause est responsable :

  1. Quelles propriétés sont animées ? Tout ce qui n'est pas transform/opacity est le premier suspect : les propriétés de layout d'abord, puis box-shadow et border-radius, qui ne ressemblent pas à du layout et sont faciles à manquer.
  2. La saccade coïncide-t-elle avec une page occupée ? Navigation, chargement, hydratation, une longue tâche. Déplacez l'animation vers CSS ou WAAPI.
  3. React se re-rend-il à chaque trame ? Mises à jour d'état dans les gestionnaires rAF, pointer ou scroll.
  4. Cela s'aggrave-t-il à mesure que le contenu grandit ? Une variable CSS héritée, ou trop de nœuds animant en même temps.
  5. Une variable CSS est-elle animée du tout ? Ou les raccourcis x/y/scale de Motion sur une version antérieure à 14.2 ?
  6. Un blur() animé au-dessus de 20px ?
  7. Ça se décale ou ça saccade encore ? Maintenant, ajoutez will-change: transform à l'élément animé.

Confirmez par un enregistrement, pas à l'instinct : le panneau Performance des DevTools pour les trames perdues et les longues tâches, le panneau Animations pour ralentir le mouvement. Profilage sur un vrai téléphone de milieu de gamme via le débogage à distance. Le throttling CPU sur un ordinateur de bureau modélise un CPU lent, pas un GPU faible ou un petit budget mémoire.

Phase 3 : Rapporter

Pour chaque résultat :

**[Élément]** : `fichier.tsx:42`
Cause : [laquelle des causes ci-dessus]
Avant : `transition: all 200ms` (anime `padding`, déclenche layout)
Après : `transition: transform 200ms` avec `scale(0.97)`
Vérifié : à partir du code / nécessite un enregistrement sur appareil

Ordonnez les résultats par coût : layout avant paint, thread-principal-occupé avant inactif. Puis arrêtez-vous. Appliquez les correctifs lorsque demandé.

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

Skills associés