Bront888/skills

Gardez les animations web dans le budget d'image — animez uniquement les propriétés de composite, maintenez le mouvement hors du thread principal lorsque la page est occupée, et trouvez la cause lorsqu'une animation perd des images. Couvre le pipeline layout/paint/composite, les échanges de propriétés moins coûteux, CSS vs WAAPI vs requestAnimationFrame, le chemin accéléré matériellement de Framer Motion, le re-rendu React par image, le recalcul des variables CSS héritées, le coût du flou, et quand `will-change` aide vraiment. Utilisez-le quand une animation est saccadée, hachée, bégaye ou perd des images ; quand vous choisissez quelle propriété animer ; quand vous décidez entre une animation CSS ou JS sur des critères de performance ; ou quand vous utilisez `will-change` ou les couches GPU. Pour choisir l'easing, la durée, ou décider d'animer ou non, utilisez « animate » ; pour une révision complète du mouvement, utilisez « review-animations ».

Qu'est-ce que skills ?

Cette compétence est conçue pour diagnostiquer et résoudre les problèmes de performance des animations web, en se concentrant sur un objectif unique : garantir que chaque image s'affiche dans les temps. Elle couvre le pipeline complet de rendu (layout, paint, composite) et explique pourquoi l'animation de propriétés comme `width` ou `height` est coûteuse, tandis que `transform` et `opacity` sont les choix privilégiés. Elle aide à identifier si la cause des saccades est un mauvais choix de propriété, un thread principal surchargé (par exemple lors de la navigation), un re-rendu React excessif, ou l'utilisation de variables CSS héritées. La compétence guide également sur l'utilisation appropriée de `will-change`, la différence entre les drivers d'animation (CSS, WAAPI, requestAnimationFrame), et comment accélérer matériellement les animations Framer Motion. Elle est applicable à tous les environnements web (React, animations natives, etc.) et fournit un workflow structuré pour auditer, diagnostiquer et corriger les performances

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

Installed? Explore more Développement et programmation skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Demander à votre IA préférée

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

Documentation

Performance des Animations

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 images 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é de question.

Compétence de diagnostic. Elle fait UNE chose : faire en sorte que le mouvement s'affiche à chaque image. Elle ne choisit pas l'easing ou la durée (c'est le rôle de 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 de animate-expo). La question ici n'est jamais « est-ce que ça a l'air correct ? » mais « chaque image se termine-t-elle à temps ? ».

Pourquoi C'est Important

Imaginez Sonner tournant à 30 ips. Même easing, mêmes durées, même code. Personne ne s'en serait soucié. La performance n'est pas une étape de polissage appliquée après que le mouvement est bon ; c'est ce qui permet à un bon mouvement de se faire remarquer.

Posture Opérationnelle

Vous êtes un ingénieur de conception qui a livré des tiroirs, des notifications et des tableaux de bord sur de vrais appareils et a été confronté à 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 de défaillance, 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 référence est un téléphone milieu de gamme, et sur un écran 120 Hz, le budget est la moitié de ce que vous pensez.
  2. Saupoudrer will-change et dire 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 nécessite une raison. Si une propriété moins chère produit le même visuel, utilisez-la, même quand 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. « J'ai ajouté will-change, ça semble plus fluide » n'est pas un diagnostic.
  3. Ne concluez jamais « c'est bon » depuis une machine rapide. Dites quels résultats vous avez vérifiés dans le code et lesquels nécessitent un enregistrement sur un vrai appareil.
  4. Jamais 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'a l'intention d'animer.
  5. Ne sacrifiez pas la correction pour une optimisation au mieux. 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 sur le second point.

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 hachéefilter: drop-shadow()
Saccades uniquement pendant la navigation ou le chargementDéplacez l'animation en CSS ou WAAPI pour qu'elle s'exécute hors du thread principal
Framer Motion x/y perd des imagesAnimez la chaîne transform complète
L'animation faite maison perd des images dans ReactÉcrivez dans ref.current.style, pas dans le state
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 (particulièrement Safari)Maintenez le blur() animé sous 20px
Des propriétés inattendues s'animentRemplacez transition: all par des propriétés explicites
L'élément se décale de 1px quand le mouvement commence ou se terminewill-change: transform, seulement quand vous le voyez

Le Budget d'Image

60 ips est ce que le cerveau perçoit comme un mouvement fluide et correspond à la plupart des taux de rafraîchissement. Cela donne au navigateur 16,7 ms (1000/60) par image. Sur un écran 120 Hz, désormais la plupart des téléphones et de nombreux ordinateurs portables, c'est 8 ms. Concevez pour le nombre le plus petit.

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

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

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

Ce Que Vous Animez

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

  • Layout : calculer la taille et la position de chaque élément.
  • Paint : dessiner la page en couches.
  • Composite : assembler ces couches à l'écran.

Chaque propriété entre dans ce pipeline à l'un des trois points suivants, et le coût diffère d'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 les promeuvent dans leur propre couche.
Paint + compositebox-shadow, border-radius, colorPas de re-mesure, mais un redessin à chaque image.
Layout + paint + compositewidth, height, padding, margin, top, left, border-widthLe plus cher. Un é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 plusieurs navigateurs et appareils ; la prise en charge est inégale.

Descendre d'un niveau :

Au lieu deAnimez
width / height / padding pour agrandir ou réduirescale()
top / left / margin pour déplacertranslate() (les pourcentages sont relatifs à la taille de l'élément lui-même)
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 image.

Deux choses en découlent :

  • Les animations de layout ne perdent pas toujours des images. Un élément en position absolue, ou avec peu d'enfants, peut animer padding sans coût visible. Mais une animation padding et une animation scale() se ressemblent, et une seule d'entre elles ne peut pas régresser sur un appareil plus lent. Choisissez celle qui n'a pas d'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 image ; 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ù Cela S'exécute

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

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

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

Fiables pour l'accélération : transform et opacity. En cours de support : filter, clip-path, background-color.

Le schéma à surveiller est le mouvement qui s'exécute pendant que la page effectue un travail lourd : transitions de page, indicateurs d'onglets pendant la navigation, tout ce qui se déclenche en même temps que le chargement des 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à, ce qui a fait perdre des images. Le déplacer vers une animation CSS a résolu le problème.

Framer Motion peut être accéléré, mais seulement lorsque la transform est une chaîne :

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

Les raccourcis x, y, scale, rotate sont implémentés avec des variables CSS, et les variables CSS animées ne sont pas accélérées. Les raccourcis se lisent mieux, donc gardez-les par défaut et passez à la forme chaîne lorsque l'animation doit survivre à un thread principal occupé.

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 des éléments de taille inconnue (c'est ainsi que Sonner et Vaul positionnent les notifications et les tiroirs), et la correction prime sur une optimisation au mieux.

React : N'animez Pas Via le State

Les bibliothèques d'animation animent en dehors du cycle de rendu de React, donc elles sont correctes. Le piège est une animation faite maison qui appelle setState à chaque image : le composant se re-rend tous les 16,7 ms et le seul rendu fait exploser le budget.

// Perd des images : un re-rendu par image
setY(nextY);

// Fluide : pas de re-rendu
ref.current.style.transform = `translateY(${nextY}px)`;
// ou avec une valeur de motion : 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 quand même des images.

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

// Recalcule les styles pour chaque descendant, à chaque image
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 sur un parent déclenche un recalcul de style pour chaque enfant. Plus il y a de contenu, plus chaque image est lente. Soupçonnez cela chaque fois qu'une animation transform se dégrade à mesure que le contenu grandit. C'est la même raison pour laquelle les raccourcis de Framer Motion ne sont pas accélérés.

Flou. filter: blur() devient rapidement coûteux, particulièrement dans Safari. Maintenez le flou animé sous 20 px ; 2 à 5 px suffisent pour adoucir un fondu enchaîné.

will-change

Un navigateur peut passer une animation du CPU au GPU en cours de route, et ce transfert peut faire décaler l'élément d'un pixel au début ou à la fin. will-change promeut l'élément en amont afin qu'aucun transfert n'ait lieu :

.drawer {
  will-change: transform;
}

Ajoutez-le seulement une fois que vous voyez le décalage ou les images perdues mesurées. Les navigateurs promeuvent déjà les éléments animant transform ou opacity, donc dans le cas courant, 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 pour 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.

Faut-il É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 offre des choses que le CSS ne peut pas : de vrais ressorts, des animations de layout partagées, des animations de sortie pour les composants démontés, des gestes avec élan. La question est de savoir si vous en avez besoin, pas si l'animation JS est autorisée.

La répartition fonctionnelle : CSS pour les animations simples et tout ce qui se joue pendant que la page est occupée ; Framer Motion pour les animations complexes, interruptibles et basées sur les gestes.

Flux de Travail

Phase 1 : Trouvez ce qui anime

Listez chaque animation dans le champ d'application avec son pilote et ses propriétés :

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

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

Phase 2 : Diagnostiquez

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

  1. Quelles propriétés sont animées ? Autre chose que transform/opacity est le premier suspect : d'abord les propriétés de layout, 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 en CSS ou WAAPI.
  3. React se re-rend-il à chaque image ? Mises à jour d'état dans les gestionnaires rAF, pointer ou scroll.
  4. Cela empire-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 ? Directement, ou via les x/y/scale de Motion.
  6. Un blur() animé au-dessus de 20 px ?
  7. Toujours un décalage ou des saccades ? Ajoutez maintenant will-change: transform à l'élément qui anime.

Confirmez avec un enregistrement, pas au toucher : le panneau Performance des DevTools pour les images perdues et les longues tâches, le panneau Animations pour ralentir le mouvement. Profilez sur un vrai téléphone milieu de gamme via le débogage à distance. La limitation du CPU sur un ordinateur de bureau modélise un CPU lent, pas un GPU faible ou un petit budget mémoire.

Phase 3 : Rapportez

Pour chaque constatation :

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

Ordonnez les constatations par coût : layout avant paint, thread-principal-occupé avant inactif. Arrêtez-vous ensuite. Appliquez les correctifs quand on vous le demande.

Skills associés