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 从公开仓库中收录这些内容。

举报或申请下架