figma-implement-motion

Par figma · mcp-server-guide

Traduit les animations et mouvements Figma en code applicatif prêt pour la production. À utiliser lors de l'implémentation d'une animation ou d'un mouvement issu d'un design Figma — l'utilisateur mentionne « implémenter ce mouvement », « ajouter une animation depuis Figma », « animer ce composant », fournit une URL Figma dont le nœud est animé, ou lorsque `get_design_context` retourne des données de mouvement ou vous demande d'appeler `get_motion_context`.

npx skills add https://github.com/figma/mcp-server-guide --skill figma-implement-motion

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és data-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 — extrais fileKey (le segment après /design/) et nodeId (la valeur du paramètre query node-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 @keyframes et 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é. Si nodeId est un id qualifié par instance tel que I4005:6111;30:8005, D2R peut afficher le corps du composant réutilisable avec l'id du composant backing à la place, tel que 4002:3957. Dans ce cas, fallbackNodeId est le data-node-id à chercher si la recherche exacte nodeId é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, timelineDurationMs et transformOrigin peuvent être omis pour réduire la payload — le snippet porte déjà duration + transform-origin (motion.dev duration / style={{ transformOrigin }}, ou CSS animation / transform-origin) et la cohort porte durationMs. 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 tags motion.* visibles dans le JSX statique. Chaque nœud retourné est animé. Fais correspondre chaque nœud motion au get_design_context par exact nodeId / data-node-id d'abord. Si et seulement s'il n'y a pas de correspondance exacte, essaie fallbackNodeId / 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. fallbackNodeId pointe 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'exact nodeId existe 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. Le data-node-id correspondant 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_context peut déjà émettre motion.{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 le data-node-id du 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, utilise fallbackNodeId pour trouver le data-node-id du 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 en contents (Tailwind contents), généralement à côté d'un absolute/inset-[…] mort (qui ne fait rien sur une boîte contents). Pour un groupe statique, garde display: contents et laisse les enfants se positionner par rapport à l'ancêtre réel le plus proche — convertir le inset du 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: contents ne 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_context est 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 un data-node-id. Parcours chaque nœud dans la réponse motion et applique sa motion à l'élément avec le data-node-id correspondant, 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 un mask-image statique), ne le lâche pas silencieusement — laisse un commentaire // TODO: <nodeId> motion unsupported et 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>. Quand get_motion_context cible 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. Le data-motion-keys du wrapper (transform tracks — x/y/rotate/scaleX/scaleY/skewX) va sur le wrapper EXTERNE ; le data-motion-keys de l'élément interne va sur l'élément INTERNE.
  • Réapplique un data-motion-transform-template. Si le wrapper en porte un, définis transformTemplate={(_, 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_context donne le transform absolu du nœud, qui inclut déjà le transform de base statique que ces divs appliquent. Un snippet rotate de [45, 125, 125] sur une base rotate-45 signifie 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/y commenç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'anime rotate, scale, ou skew, vérifie qu'il ne s'appuie aussi pas sur les transforms de layout Tailwind tels que -translate-x-1/2 ou -translate-y-1/2 pour le centrage/positionnement. Ces utilitaires partagent la propriété CSS transform que 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ément motion.* 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 la durationMs / loopMode de la cohort et en lisant transformOrigin / 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.

  1. Respecte les valeurs de la sortie de l'outil, pas son layout. Préserve le timing exact, l'easing, les valeurs keyframe et le transformOrigin de codeSnippets — ne les régénère pas à partir de keyframeBindings ou 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). transformOrigin est 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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é ».
  6. Ne télécharge pas un asset juste pour le Read. get_design_context / get_motion_context retournent 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>, CSS background-image, une importation d'asset), ou curl un pour inliner ses contenus (ex. inline le SVG et affiche via NSImage(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'outil Read : 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.
  7. 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 variants paramé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 @keyframes avec shorthand animation, retourné directement par l'outil.
  • SwiftUI : Modifieurs .animation(...) natifs, traduits du snippet CSS (get_motion_context n'émet pas de code SwiftUI, mais les clients SwiftUI/iOS obtiennent quand même le format CSS ; bascule vers keyframeBindings / 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.

Skills similaires