Framer Motion

AThevon/genjutsu/skills/_jutsu/framer-motion

作者 AThevon1f518a71378b15d423389e546e767cffc86e14c3無授權條款431 個星標收錄於 2026年10月9日更新於 2026年10月9日儲存庫3 天前更新

Framer Motion / Motion sub-skill - AnimatePresence, layout animations, gestures, motion values.

AI 產生的概覽

使用 Framer Motion / Motion React 動畫函式庫的參考指南,涵蓋 AnimatePresence、版面動畫、手勢與 motion values。

功能
這個技能是一份關於 Framer Motion(現已更名為 Motion)React 動畫函式庫的參考文件。它說明套件名稱與匯入方式的選擇、何時該優先使用 Framer Motion 而非 GSAP 或原生 CSS,以及如何使用 AnimatePresence 離場動畫、版面動畫、variants 編排、手勢與 motion values。它也列出常見錯誤,例如缺少 key、在回呼中未加保護地更新 state,以及搭配 styled-components 或 Emotion 時的屬性外洩。它產出的是指引與程式碼範例,而不是檔案或可執行的產物。
適用情境
在實作或審查使用 Framer Motion 或 Motion 的 React UI 動畫時使用,例如模態視窗、提示訊息、清單重排、共享版面轉場、拖曳與懸停手勢或捲動驅動效果。在需要為特定動畫需求在 Framer Motion、GSAP 與原生 CSS 之間做選擇時也適用。
執行需求
需要一個使用 motion 或 framer-motion 套件的 React 專案(同儕相依套件 react 與 react-dom ^18 或 ^19)。不包含指令碼;這個技能是說明文件加一份參考文件。涉及版本的內容標註了日期,使用前應重新查證。

Version-sensitive. Every API name, SDK gate and browser-support claim below was verified on 2026-09-08 against primary sources. What against, and when, is in _jutsu/VERSIONS.md. If that date is old, re-verify before acting on a version number.

Framer Motion - Sub-skill

Two package names, one library. Framer Motion was renamed to Motion. motion and framer-motion both publish the same version (13.2.0 as of 2026-09-08): motion declares "framer-motion": "^13.2.0" as a dependency and motion/react re-exports it. Neither is broken, and framer-motion is still the one most installed projects have.

Read package.json and follow what is there. Do not migrate a project from one to the other unless the user asks - that is Iron Rule 8 in cast / 10 in paint.

In package.jsonImport fromInstall line
motion"motion/react"npm install motion
framer-motion"framer-motion"npm install framer-motion
bothwhichever the file you are editing already imports; say the project is mid-migration-

Every API in this skill is identical across the two names at v13. Where a symbol is newer than v11, it is flagged inline. Peers: react / react-dom ^18 || ^19.

When to use Framer Motion vs alternatives

CriteriaFramer MotionGSAPNative CSS
Layout animationsExcellent (layoutId)ManualImpossible
Exit animationsAnimatePresenceTimeline reverseLimited (display)
Gestures (drag, hover)Native, declarativeDraggable pluginBasic
Scroll-drivenuseScroll + useTransformScrollTrigger (more powerful)scroll-timeline
Complex orchestrationVariants + propagationTimeline (more flexible)@keyframes
Bundle size~50kb tree-shaken~30kb core0kb
React integrationNative, component-firstRefs + useGSAPclassName toggle

Rule: Framer Motion for React UI interactions (modals, toasts, reorder, shared layout). GSAP for complex timelines, cinematic scroll-driven, SVG morphing.

AnimatePresence - Exit animations

tsx
<AnimatePresence mode="wait">  {isVisible && (    <motion.div      key="unique-key"        // REQUIRED - identifies the component      initial={{ opacity: 0 }}      animate={{ opacity: 1 }}      exit={{ opacity: 0 }}    />  )}</AnimatePresence>
  • mode="wait" - waits for exit to finish before enter (page transitions)
  • mode="sync" - exit and enter simultaneously
  • mode="popLayout" - removes from flow immediately (good for lists)
  • onExitComplete - callback when all exit animations are finished

Layout animations

tsx
// Shared layout - the element "slides" between two positions<motion.div layoutId="highlight" className={activeTab === id ? "active" : ""} />
// Auto layout - animates position/size when layout changes<motion.div layout>  {isExpanded && <motion.p layout>Additional content</motion.p>}</motion.div>
// layout="position" - animates position only (not size)// layout="size" - animates size only// layout="preserve-aspect" - preserves the ratio during the transition

Variants - Propagation and orchestration

tsx
import { stagger } from "motion/react";
const container = {  hidden: { opacity: 0 },  show: {    opacity: 1,    transition: {      // staggerChildren + staggerDirection are DEPRECATED (Motion 12.22, Jul 2025).      // Pass a stagger() function to delayChildren instead.      delayChildren: stagger(0.08, { startDelay: 0.2 }),      // reverse order: stagger(0.08, { from: "last" })      // also available: from: "first" | "center" | "last" | index, and ease    },  },};
const item = {  hidden: { opacity: 0, y: 20 },  show: { opacity: 1, y: 0 },};
<motion.ul variants={container} initial="hidden" animate="show">  {items.map((i) => (    <motion.li key={i.id} variants={item} />  ))}</motion.ul>

Variants automatically propagate to motion children - no need for initial/animate on children.

Gestures

tsx
<motion.div  whileHover={{ scale: 1.05 }}  whileTap={{ scale: 0.95 }}  whileFocus={{ borderColor: "#3b82f6" }}  // Drag  drag               // true = x+y, "x" = horizontal only, "y" = vertical only  dragConstraints={{ left: -100, right: 100, top: -50, bottom: 50 }}  dragElastic={0.2}  // 0 = rigid, 1 = free (default 0.35)  dragSnapToOrigin   // returns to initial position  onDragEnd={(e, info) => {    if (info.offset.x > 100) handleSwipe("right");  }}/>

Motion values - Reactive without re-render

tsx
const x = useMotionValue(0);const opacity = useTransform(x, [-200, 0, 200], [0, 1, 0]);const background = useTransform(x, [-200, 200], ["#ff0000", "#00ff00"]);
// Spring-based smoothingconst smoothX = useSpring(x, { stiffness: 300, damping: 30 });
// Scroll trackingconst { scrollY, scrollYProgress } = useScroll();const parallaxY = useTransform(scrollYProgress, [0, 1], [0, -300]);
// Element-scoped scrollconst ref = useRef(null);const { scrollYProgress } = useScroll({  target: ref,  offset: ["start end", "end start"],});

Motion values do NOT trigger React re-renders - they update the DOM directly via style.

Do Not

Do not use motion with styled-components / Emotion without isValidProp (v13+)

Motion 13.0 removed @emotion/is-prop-valid as an optional dependency. Without explicit injection, motion-only props leak onto the DOM.

tsx
import isPropValid from "@emotion/is-prop-valid";import { MotionConfig } from "motion/react";
<MotionConfig isValidProp={isPropValid}>  <App /></MotionConfig>

Or reverse the composition so the styling library owns prop forwarding: const MotionDiv = motion.create(StyledDiv).

Do not setState in callbacks without a guard

tsx
// BAD - infinite re-render if animate depends on stateonUpdate={(latest) => setPosition(latest.x)}
// GOOD - guard or useMotionValueEventconst x = useMotionValue(0);useMotionValueEvent(x, "change", (latest) => {  if (latest > threshold) onThresholdReached();});

Do not use layout animation without a stable key

tsx
// BAD - key changes every render, breaks layout tracking<motion.div layout key={Math.random()} />
// GOOD - stable key derived from data<motion.div layout key={item.id} />

Do not forget the unique key on AnimatePresence

tsx
// BAD - no key, exit animation does not trigger<AnimatePresence>  {isOpen && <motion.div exit={{ opacity: 0 }} />}</AnimatePresence>
// GOOD - unique key for each conditional child<AnimatePresence>  {isOpen && <motion.div key="modal" exit={{ opacity: 0 }} />}</AnimatePresence>

Do not wrap an already animated component with motion.div

tsx
// BAD - double animation, transform conflicts<motion.div animate={{ x: 100 }}>  <motion.div animate={{ x: -50 }}>Content</motion.div></motion.div>
// GOOD - single animation level per transform axis<motion.div animate={{ x: 100 }}>  <motion.div animate={{ opacity: 0.5 }}>Content</motion.div></motion.div>
// GOOD - use variants to coordinate parent/child<motion.div variants={parent} animate="active">  <motion.div variants={child} /></motion.div>

來源與署名

來源:AThevon/genjutsu位於skills/_jutsu/framer-motion提交1f518a7

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架