Implémenter Motion
Vue d'ensemble
Cette skill guide la traduction des animations et transitions Figma en code exécutable (motion.dev, CSS keyframes ou bibliothèques spécifiques aux frameworks).
Figma expose la motion via deux outils :
get_motion_context— outil motion faisant autorité. Retourne l'inventaire complet des nœuds animés, des snippets de code précompilés (CSS@keyframes+ motion.dev), des liaisons keyframe de secours quand les snippets ne sont pas disponibles, et des indices de coordination timeline récursifs (timelineCohorts). Source de vérité pour les données d'animation et les ID de nœud qui s'animent.get_design_context— la structure du design : layout, dimensionnement, assets, styling, indices Code Connect, contexte screenshot, et parfois marqueurs de placement motion sur les éléments animés (data-node-id, et sur les nœuds divisésdata-motion-keys/data-motion-wrapper-for/data-motion-transform-template). Il peut afficher un nœud animé comme un élément simple (div,p,span, etc.) ou un élément motion (motion.div) ; il n'inclut pas les valeurs d'animation inline.
Les deux sont liés par node id, et c'est tout le workflow. get_motion_context te dit quels nœuds s'animent et donne les valeurs keyframe, l'easing, le timing et les snippets. get_design_context te dit à quoi ressemblent ces nœuds et où ils se situent. Pour chaque nœud dans get_motion_context.nodes, trouve le data-node-id correspondant dans le contexte design et fusionne la motion dans cette structure — en ajoutant ou enveloppant un motion.{tag} quand l'élément structurel est simple. Quand le contexte design a réutilisé un composant Figma, le nœud motion peut aussi inclure fallbackNodeId ; utilise-le uniquement comme fallback après avoir essayé l'exact nodeId.
Limites de la skill
- Utilise cette skill quand le livrable est du code motion dans le repository de l'utilisateur.
- Si l'utilisateur demande de créer/éditer des animations à l'intérieur de Figma, bascule vers figma-use et suis plutôt cette skill.
- Cette skill couvre actuellement les animations émises par
get_motion_context(snippets plus fallback keyframe tracks, incluant la motion résolue via preset dans ces formes). Les flux de variant interactif plus larges peuvent encore nécessiter une gestion d'état spécifique au produit dans le code.
Prérequis
- Serveur Figma MCP connecté et accessible.
- Node ID parsé depuis l'URL Figma fournie par l'utilisateur. Format d'URL :
https://figma.com/design/:fileKey/:fileName?node-id=1-2— extraisfileKey(le segment après/design/) etnodeId(la valeur du paramètre querynode-id, ex.42-15). - Codebase cible. Le format de sortie motion s'adapte à la stack (voir Framework Recommendations).
Choix de l'outil
Pour l'implémentation motion, utilise les deux outils avec des rôles distincts :
| Situation | Outil | Pourquoi |
|---|---|---|
| Comprendre la structure statique, assets, styles, Code Connect, ou layout visuel | get_design_context |
Donne la référence de code du composant/page et les URLs d'assets dont tu as besoin pour placer les nœuds animés correctement. |
| Récupérer les données d'animation pour n'importe quel nœud | get_motion_context |
Construit spécifiquement pour la motion et source de vérité pour le timing, l'easing, les snippets et keyframes. |
Un nœud a des marqueurs motion (data-motion-keys, data-motion-wrapper-for) |
Marqueurs pour le placement divisé, get_motion_context pour les valeurs |
Les marqueurs divisés te disent quelles tracks vont sur quel élément ; les keyframes/easing/timing et l'inventaire des nœuds animés viennent de get_motion_context. |
get_motion_context accepte recursive: true (limité à 500 nœuds) quand tu as besoin de la motion des descendants en un seul appel.
Workflow obligatoire
Étape 1 : Confirmer que le contexte design statique est disponible
get_design_context(fileKey=":fileKey", nodeId="<node-id>")
Si get_design_context a déjà été appelé pour ce nœud, réutilise cette sortie. Sinon, appelle-le normalement maintenant.
Utilise-le comme la structure faisant référence — hiérarchie, dimensionnement, styling, assets, indices Code Connect, contexte screenshot, et tout marqueur motion de placement qu'il pourrait inclure (Étape 3). L'inventaire des nœuds animés et les valeurs d'animation proviennent de get_motion_context (Étape 2).
Étape 2 : Récupérer les données motion faisant autorité
get_motion_context(fileKey=":fileKey", nodeId="<node-id>", recursive=true)
Forme de réponse (une entrée par nœud animé) :
codeSnippets— strings CSS@keyframeset motion.dev pré-générés. Utilise-les directement. Ne les régénère pas à partir des données fallback track.keyframeBindings— bound keyframe tracks, incluant la motion dérivée preset résolue en données track, incluse uniquement comme données fallback quand les deux formats de snippet manquent.motionSummary— description en langage naturel une ligne par field de l'animation. Présente uniquement quand il n'y a pas de snippet (la codegen motion keyframe-bindings-only ne pouvait pas s'exprimer en CSS/motion.dev). Construis à partir de lui quand présent ; ignore-le chaque fois qu'un snippet existe.fallbackNodeId— id fallback optionnel pour faire correspondre le contexte design componentisé. SinodeIdest un id qualifié par instance tel queI4005:6111;30:8005, D2R peut afficher le corps du composant réutilisable avec l'id du composant backing à la place, tel que4002:3957. Dans ce cas,fallbackNodeIdest ledata-node-idà chercher si la recherche exactenodeIdéchoue.
Les réponses récursives incluent aussi timelineCohorts — un array top-level (non par nœud) de nœuds partageant une timeline : { rootNodeId, durationMs, loopMode: 'once' | 'loop' | 'boomerang', memberNodeIds[] }. Pour la motion multi-nœud coordonnée, pilote tous les memberNodeIds à partir d'un cycle de vie partagé utilisant durationMs (÷1000 pour les secondes) et loopMode — ne déduis pas le timing de l'ordre des siblings.
Détails d'implémentation qui importent pour les LLMs :
- Quand un snippet existe,
motionSummary,timelineDurationMsettransformOriginpeuvent être omis pour réduire la payload — le snippet porte déjà duration + transform-origin (motion.devduration/style={{ transformOrigin }}, ou CSSanimation/transform-origin) et la cohort portedurationMs. Un field manquant ne signifie jamais « pas d'animation ». - Les réponses récursives déduplicatent les snippets exactement identiques. Un snippet peut être remplacé par un commentaire pointant vers le premier nœud avec une motion identique ; réutilise le même composant, variant, classe ou constantes au lieu d'écrire une deuxième animation.
- Le serveur MCP déduit les snippets CSS vs motion.dev de
clientFrameworks; si la réponse ne contient qu'un seul format de snippet, adapte ce format à la stack de l'utilisateur plutôt que de supposer que l'autre format a échoué.
Étape 3 : Fusionner le contexte statique et motion
- Commence par
get_motion_context.nodes, pas par les tagsmotion.*visibles dans le JSX statique. Chaque nœud retourné est animé. Fais correspondre chaque nœud motion auget_design_contextpar exactnodeId/data-node-idd'abord. Si et seulement s'il n'y a pas de correspondance exacte, essaiefallbackNodeId/data-node-id. Bascule vers le nom/type du nœud et la position screenshot uniquement après que les deux ids échouent. - La correspondance exacte de l'id l'emporte sur
fallbackNodeId.fallbackNodeIdpointe vers l'id du composant backing que D2R peut émettre à l'intérieur d'un composant réutilisable. Il est partagé par chaque instance de ce composant. Si l'exactnodeIdexiste dans le contexte design, applique la motion là et ignore le fallback. C'est critique pour l'animation root-instance : une instance peut tourner ou se déplacer différemment d'une autre instance du même composant, et appliquer cette motion au corps du composant partagé animerait toutes les instances incorrectement. - Applique chaque nœud motion à la structure du contexte design correspondante, clé par
data-node-id. Ledata-node-idcorrespondant est l'ancre structurelle, pas toujours l'élément DOM final qui reçoit la motion. Utilise la forme du snippet et les marqueurs de placement pour décider si la motion va sur cet élément exact, un wrapper, un élément interne, ou un SVG path inline.get_design_contextpeut déjà émettremotion.{tag}avec les valeurs dépouillées, ou il peut émettre un élément structurel simple (div,p,span, racine du composant, etc.). S'il est simple et que le snippet cible l'élément lui-même, convertis-le en l'appropriémotion.{tag}ou ajoute un wrapper motion tout en préservant le texte, les enfants, les classes/styles, les attributs et ledata-node-iddu nœud. Charge references/examples-and-anti-examples.md pour voir des exemples de cette étape de fusion. - La motion des enfants componentisés correspond généralement par fallback. Quand le contexte design extrait une instance Figma en un composant React réutilisable, les enfants à l'intérieur du corps du composant ont souvent des ids de composant backing (
4002:3957) tandis que le contexte motion rapporte les ids d'instance live (I4005:6111;30:8005). Dans ce cas, utilisefallbackNodeIdpour trouver ledata-node-iddu corps du composant, mais garde la motion délimitée à l'instance rendue que tu impléments. S'il y a plusieurs instances et que seule l'une a une motion root différente, la correspondance exacte de l'id garde cette motion par-instance séparée. - Les nœuds divisés portent un marqueur
data-motion-keys/data-motion-wrapper-for— voir Gestion des transforms interleaved ci-dessous. - Préserve les wrappers
display: contents— à moins que le groupe lui-même ne s'anime. Les wrappers de groupes layout-transparent arrivent encontents(Tailwindcontents), généralement à côté d'unabsolute/inset-[…]mort (qui ne fait rien sur une boîtecontents). Pour un groupe statique, gardedisplay: contentset laisse les enfants se positionner par rapport à l'ancêtre réel le plus proche — convertir leinsetdu wrapper en une boîte positionnée reparentalise les enfants à une plus petite boîte, donc ils se rendent trop petits / décalés vers l'intérieur. Pour un groupe animé (le nœud du groupe lui-même a une motion),display: contentsne peut pas porter un transform — remplace-le avec un wrapper positionné réel et applique la motion du groupe là. Charge references/gotchas.md avant d'implémenter ce cas. get_motion_contextest l'inventaire complet des nœuds animés. Certains nœuds animés se rendent en éléments simples (non-motion) — racines d'instance du composant (simple<div>de positionnement), texte (<p>), masques — qui portent quand même undata-node-id. Parcours chaque nœud dans la réponse motion et applique sa motion à l'élément avec ledata-node-idcorrespondant, enveloppant ou convertissant selon les besoins. Si un nœud animé n'a aucun élément du tout dans la sortie (ex. un masque animé aplati en unmask-imagestatique), ne le lâche pas silencieusement — laisse un commentaire// TODO: <nodeId> motion unsupportedet appelle-le dans ton résumé.- Si un nœud apparaît dans le contexte motion mais pas dans le JSX statique, ajoute l'élément nécessaire pour le représenter — le code du contexte design est une référence, pas un inventaire d'animation complet.
- En cas de conflit entre les contextes design et motion (timing/easing/valeurs animées), préfère
get_motion_context. - Motion SVG au niveau du path : inline le SVG et anime le vrai
<path>. Quandget_motion_contextcible le path d'un vecteur (PATH_TRIM,motion.path,stroke-dasharray) mais que le contexte design l'affiche en<img>, inline le SVG et applique le snippet au<path>, en gardant le wrapper de layout. Charge references/svg-and-path-motion.md pour le complet how-to de ce cas (motion.path,pathLength="1", layering wrapper+path, CSS path-trim).
Gestion des transforms interleaved
Un nœud avec à la fois un transform de base statique et des transforms animés est divisé entre éléments imbriqués pour que les deux se composent correctement au lieu d'entrer en conflit : un motion.div sans id portant data-motion-wrapper-for="<nodeId>" (le wrapper EXTERNE) enveloppe un div de transform statique (ex. rotate-45 + sizing hypot() — ou le wrapper lui-même porte data-motion-transform-template="<css>") qui enveloppe le nœud INTERNE (data-node-id). Garde l'imbrication wrapper > static-transform div > inner — la collapsing la brise et le transform de base.
- Place les tracks par
data-motion-keys. Ledata-motion-keysdu wrapper (transform tracks —x/y/rotate/scaleX/scaleY/skewX) va sur le wrapper EXTERNE ; ledata-motion-keysde l'élément interne va sur l'élément INTERNE. - Réapplique un
data-motion-transform-template. Si le wrapper en porte un, définistransformTemplate={(_, generated) => "<css> " + generated}pour que le transform animé se compose au-dessus de ce transform layout statique. - Décale le transform animé par la base statique (évite la rotation double).
get_motion_contextdonne le transform absolu du nœud, qui inclut déjà le transform de base statique que ces divs appliquent. Un snippetrotatede[45, 125, 125]sur une baserotate-45signifie que le wrapper anime le décalage[0, 80, 80](= absolu − 45), pas l'absolu — sinon le 45° s'applique deux fois et l'élément se tient à 90° au repos. Les tracks sans base statique (ex.x/ycommençant à 0) passent inchangés. Voir l'exemple interleaved-transform. - Garde les transforms de layout séparés des transforms Motion. Pour chaque élément
motion.*qui s'animerotate,scale, ouskew, vérifie qu'il ne s'appuie aussi pas sur les transforms de layout Tailwind tels que-translate-x-1/2ou-translate-y-1/2pour le centrage/positionnement. Ces utilitaires partagent la propriété CSStransformque Motion.dev écrit inline, donc le transform de Motion peut effacer le layout translate. Si les deux sont nécessaires, divise l'élément en un wrapper layout statique portant le transform de centrage/positionnement et un élémentmotion.*interne portant les rotate/scale/opacity animés, ou encode le décalage layout dans Motion lui-même (x: "-50%") et garde-le présent pour chaque keyframe.
Étape 4 : Applique la motion dans le code
- motion.dev présent dans les snippets ? Utilise le code motion.dev verbatim pour les cibles React. Importe depuis
motion/react— à moins que la codebase n'utilise déjà une autre bibliothèque motion (Framer Motion, React Spring, GSAP), auquel cas adapte le snippet à elle. Charge references/framework-recommendations.md quand tu adaptes à une autre stack ou choisis une bibliothèque. - CSS keyframes présents ? Utilise pour les cibles vanilla/non-React, ou quand la codebase n'a pas de bibliothèque motion React.
- Pas de snippets (keyframe-bindings-only) ? Construis la motion.dev/CSS équivalente à partir de
keyframeBindings+motionSummary, en prenant le timing de loop de ladurationMs/loopModede la cohort et en lisanttransformOrigin/ duration à partir des fields structurés. Rare — les snippets sont normalement présents, incluant pour SwiftUI/iOS (qui obtiennent le format CSS).
Étape 5 : Valide
- Lis les imports motion existants du composant et les conventions avant d'en ajouter de nouveaux. Si l'utilisateur utilise déjà Framer Motion / React Spring / anime.js, adapte plutôt que de forcer motion.dev.
- Spot-check une animation s'exécute de bout en bout (reload, observe, itère) avant de regrouper les changements sur plusieurs nœuds.
- Charge references/gotchas.md, qui couvre les bugs motion spécifiques à Figma et leurs fixes, et corrige tout tel cas dans le code généré.
Règles critiques
Ce sont les principes généraux. Les gotchas spécifiques (pivots de rotation, sémantique HOLD, interpolation de couleur, etc.) vivent dans les references catégorisées. Quand une référence liée est mentionnée dans ce texte de skill et que la situation s'applique, charge ce fichier avant de continuer.
- Respecte les valeurs de la sortie de l'outil, pas son layout. Préserve le timing exact, l'easing, les valeurs keyframe et le
transformOrigindecodeSnippets— ne les régénère pas à partir dekeyframeBindingsou des fields structurés quand les snippets existent (régénérer perd la fidelité sur les custom bezier easings, les spring approximations et les valeurs overshoot).transformOriginest par élément : applique le propre de chaque nœud scaling/rotating — incluant les nested scalers, pas juste le wrapper externe — ou l'élément pivote depuis le centre par défaut et se développe/tourne du mauvais coin (voir l'exemple per-element-transformOrigin). Mais le snippet est les données d'un nœud, pas un template copy-paste : quand de nombreux nœuds le partagent, factorise-le par Règle 7 au lieu de paster le bloc N fois. - Fais correspondre la motion stack existante de l'utilisateur. Lis les imports du composant et n'importe quelles animations siblings avant d'ajouter des dépendances. Si l'utilisateur a déjà Framer Motion, React Spring, anime.js, GSAP — adapte la sortie à leur stack plutôt que de forcer motion.dev.
- Honore
prefers-reduced-motion. N'importe quelle motion ajoutée doit s'adoucir ou se désactiver sous@media (prefers-reduced-motion: reduce)— généralement ignore l'animate(affiche l'état initial/rest) ou réduis la durée à quasi-zéro. C'est un défaut d'accessibilité, pas un opt-in. - Valide une animation de bout en bout avant de regrouper. Construis, reload, et regarde une timeline complète looser — confirme chaque nœud animé apparaît au moment où sa keyframe track dit qu'il le devrait. « Se rend sans erreur » n'est pas « se rend correctement ». Les défaillances motion se composent quand tu regroupe — un mauvais easing sur un nœud est facile à repérer ; le même bug sur vingt nœuds est des heures de démêlage.
- Ne fabrique pas la motion. Si un nœud n'a pas de données motion dans la réponse, laisse-le statique. Ne réutilise pas l'easing/durée defaults d'ailleurs dans le design, et n'auto-anime pas « parce que le reste du composant est animé ».
- Ne télécharge pas un asset juste pour le
Read.get_design_context/get_motion_contextretournent les assets en tant qu'URLs (/api/mcp/asset/...), souvent SVG. Référence l'URL directement où le consommateur la récupère (un<img src>, CSSbackground-image, une importation d'asset), oucurlun pour inliner ses contenus (ex. inline le SVG et affiche viaNSImage(data:)sur SwiftUI). L'exception importante est la motion SVG au niveau du path : si le snippet motion cible un path à l'intérieur d'un asset SVG, inline le SVG et anime le vrai path au lieu de le laisser derrière un<img>. Ne télécharge pas un asset et ne nourris pas le fichier à l'outilRead: SVG n'est pas un format d'image Read-able, donc le read est rejeté et gaspillé — et un outil de fichier qui ne détecte pas SVG-as-image peut verrouiller la boucle dessus. - Factorise la motion répétée — ne copy-paste jamais le snippet par élément. De nombreux nœuds partagent généralement la même animation ne différant que par un délai stagger, un décalage ou une valeur cible. Implémente la motion partagée une fois — un composant animé réutilisable ou un objet
variantsparamétrisé par les valeurs qui varient — affiche à partir d'un array mappé (items.map(...)), et tire les littéraux répétés (durées, arrays easing, décalages) dans des constantes nommées. Les valeurs de l'animation restent verbatim du snippet (Règle 1) ; le code reste DRY. Le même objet transition pâté 15+ fois (800 lignes qui devraient être 150) est un résultat de basse qualité — la fidelité et la maintenabilité sont toutes deux notées.
Recommandations framework
La Règle 2 couvre la posture générale : préfère la stack existante de l'utilisateur. Quand aucune n'existe, valeurs par défaut :
- React : motion.dev (le package
motion). L'outil retourne le code motion.dev directement — utilise-le. - Vanilla / web non-React : CSS
@keyframesavec shorthandanimation, retourné directement par l'outil. - SwiftUI : Modifieurs
.animation(...)natifs, traduits du snippet CSS (get_motion_contextn'émet pas de code SwiftUI, mais les clients SwiftUI/iOS obtiennent quand même le format CSS ; bascule verskeyframeBindings/motionSummary/ cohort uniquement quand snippet-less). Utilise uniquement les APIs SwiftUI réelles — aucun modifier n'accepte directement un easing Figma/CSS, donc charge references/framework-recommendations.md, mappe l'easing à son équivalent SwiftUI et vérifie plutôt que d'inventer. Ce chemin évolue ; confirme avec l'utilisateur si tu ne es pas certain.
Pour les classes d'effet établies, préfère une bibliothèque au CSS hand-rolled. Les effets comme le glass/glassmorphism, confetti, systèmes de particules, interactions basées sur la physique et la motion scroll-linked ont des implémentations de bibliothèque testées en combat qui gèrent les quirks cross-browser, l'accessibilité et la performance bien mieux que les keyframes générées. Charge references/framework-recommendations.md pour la table complète library-by-effect-class. Surface ces comme recommandations, pas des mandats — l'utilisateur décide.
Exemples
Charge references/examples-and-anti-examples.md quand tu as besoin d'exemples travaillés ou de patterns d'échec. Elle couvre le flux de fusion simple, les éléments texte simples qui ont besoin du motion.* ajouté, les transforms statiques+animés interleaved, la motion SVG au niveau du path, et des anti-exemples pour la reconstruction DOM, la dérive node-id/position, et le transformOrigin per-element manquant.
Références
Six deep dives, récupérées à la demande. Les préoccupations frontend générales (performance, unités, mécaniques d'accessibilité) sont gérées par les règles critiques ci-dessus — ces références se concentrent uniquement sur le signal spécifique à Figma. Si cette skill nomme l'un de ces fichiers dans une instruction inline, charge ce fichier avant de continuer avec cette partie de la tâche.
- references/examples-and-anti-examples.md — exemples travaillés et patterns d'échec. Charge quand tu appliques le workflow de fusion, tu gères les transforms interleaved, ou tu vérifies si une implémentation générée a reconstruit le DOM, échangé les positions des nœuds ou déposé le
transformOrigin. - references/gotchas.md — bugs motion spécifiques à Figma et leurs fixes. Rotation/scale origin sur les groupes imbriqués, sémantique HOLD easing, préservation CUSTOM_SPRING, ambiguïté d'axis scaling indépendante, interpolation de couleur. Charge quand tu dépannes le comportement runtime inattendu. Charge toujours references/motion-lint-rules.md avec ce fichier — les entrées gotcha référencent des lint rules spécifiques qui doivent être surfacées à l'utilisateur.
- references/svg-and-path-motion.md — implémentation de la motion qui cible un SVG vector path (inline l'asset,
motion.path,pathLength="1", layering wrapper+path, CSS path-trim). Charge quand un snippet du vecteur cible le path, pas un transform wrapper. - references/framework-recommendations.md — motion.dev, CSS keyframes, valeurs par défaut SwiftUI, table library-by-effect-class (glass, confetti, particles, physics, scroll-linked). Charge avant de hand-roll un effet.
- references/unsupported-and-fallbacks.md — Figma motion features qui n'exportent pas proprement aujourd'hui (text animations, path animations, masks/booleans, variants/transitions). Inclut la guidance fallback video/lottie. Charge quand la réponse de l'outil semble incomplète. Charge toujours references/motion-lint-rules.md avec ce fichier — les entrées unsupported référencent des lint rules spécifiques qui doivent être surfacées à l'utilisateur.
- references/motion-lint-rules.md — Linting rules : limitations d'export connues (erreurs et avertissements) qui doivent être surfacées à l'utilisateur. Charge quand tu génères du code motion pour vérifier si des limitations actives s'appliquent.