motion-design-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMotion Design Patterns
Motion 设计模式
Framer Motion (Motion) patterns for React — springs, staggers, layout animations, micro-interactions, scroll-triggered effects, and exit animations. The #1 differentiator between generic and polished UI.
适用于React的Framer Motion(Motion)模式——包含弹簧动画、序列动画、布局动画、微交互、滚动触发效果以及退出动画。这是普通UI与精致UI之间的核心区别所在。
When to Use
适用场景
- Building or improving UI animations in a React project
- User asks for "polish", "delight", "micro-interactions", or "make it feel good"
- Adding entrance/exit animations, hover effects, or page transitions
- Making lists, cards, modals, or navigation feel premium
- User references Magic UI, Motion Primitives, or Framer Motion
- 在React项目中构建或优化UI动画
- 用户提出“提升精致度”“增加愉悦感”“添加微交互”或“让界面更有质感”的需求
- 添加入场/退场动画、悬停效果或页面过渡动画
- 让列表、卡片、模态框或导航栏具备高端质感
- 用户提及Magic UI、Motion Primitives或Framer Motion
Core Philosophy
核心理念
- Motion should be purposeful. Every animation should communicate something — state change, hierarchy, spatial relationship, or feedback.
- Less is more. One well-tuned spring beats five competing animations.
- Performance first. Animate and
transformonly. Never animateopacity,width,height,top, orleft.margin - Consistency matters. Use the same spring configs throughout a project.
- 动画应具备目的性:每一个动画都要传递特定信息——状态变化、层级关系、空间关联或操作反馈。
- 少即是多:一个调校得当的弹簧动画效果远胜于五个相互竞争的动画。
- 性能优先:仅对和
transform属性执行动画。绝不要对opacity、width、height、top或left执行动画。margin - 保持一致性:在整个项目中使用统一的弹簧配置。
Dependencies
依赖安装
bash
npm install motionImport:
import { motion, AnimatePresence, stagger, useScroll, useTransform } from "motion/react"Note: The package was renamed from to in late 2024. Both work, but is the current package.
framer-motionmotionmotionbash
npm install motion导入方式:
import { motion, AnimatePresence, stagger, useScroll, useTransform } from "motion/react"注意:该包在2024年末从更名为。两个包都可以使用,但是当前的官方包。
framer-motionmotionmotionSpring Configurations
弹簧配置
Springs feel more natural than easing curves. Use these as your defaults:
弹簧动画比缓动曲线更具自然感。请将以下配置作为默认选项:
Recommended Defaults
推荐默认配置
tsx
// Snappy — buttons, toggles, small elements
const snappy = { type: "spring", stiffness: 500, damping: 30 }
// Smooth — cards, panels, modals
const smooth = { type: "spring", stiffness: 300, damping: 25 }
// Gentle — page transitions, large elements
const gentle = { type: "spring", stiffness: 200, damping: 20 }
// Bouncy — playful UI, notifications, badges
const bouncy = { type: "spring", stiffness: 400, damping: 15 }tsx
// 灵动型 — 按钮、开关、小型元素
const snappy = { type: "spring", stiffness: 500, damping: 30 }
// 流畅型 — 卡片、面板、模态框
const smooth = { type: "spring", stiffness: 300, damping: 25 }
// 柔和型 — 页面过渡、大型元素
const gentle = { type: "spring", stiffness: 200, damping: 20 }
// 弹跳型 — 趣味UI、通知、徽章
const bouncy = { type: "spring", stiffness: 400, damping: 15 }Quick Reference
速查表
| Feel | stiffness | damping | Use for |
|---|---|---|---|
| Snappy | 500 | 30 | Buttons, toggles, chips |
| Smooth | 300 | 25 | Cards, panels, modals |
| Gentle | 200 | 20 | Page transitions, heroes |
| Bouncy | 400 | 15 | Notifications, badges, fun UI |
| 质感 | 刚度 | 阻尼 | 适用场景 |
|---|---|---|---|
| 灵动型 | 500 | 30 | 按钮、开关、芯片组件 |
| 流畅型 | 300 | 25 | 卡片、面板、模态框 |
| 柔和型 | 200 | 20 | 页面过渡、首屏区域 |
| 弹跳型 | 400 | 15 | 通知、徽章、趣味UI |
Rules of Thumb
经验法则
- Higher stiffness = faster animation
- Lower damping = more bounce
- damping ratio < 1 = will overshoot (bounce)
- For no bounce, set damping ≥ 2 × √stiffness
- 刚度越高 = 动画速度越快
- 阻尼越低 = 弹跳效果越明显
- 阻尼比 < 1 = 会出现过冲(弹跳)效果
- 若要无弹跳效果,请设置阻尼 ≥ 2 × √刚度
Pattern 1: Fade + Rise Entrance
模式1:淡入+上滑入场
The bread-and-butter entrance animation. Element fades in while sliding up slightly.
tsx
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ type: "spring", stiffness: 300, damping: 25 }}
>
Content here
</motion.div>When to use: Cards, sections, any content appearing on the page.
Anti-pattern: Don't use or large values — subtle (12–24px) feels premium, large feels janky.
y: 100这是最基础的入场动画。元素在淡入的同时轻微向上滑动。
tsx
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ type: "spring", stiffness: 300, damping: 25 }}
>
内容区域
</motion.div>适用场景:卡片、区块、页面中出现的任意内容。
反模式:不要使用或过大的数值——细微的移动(12–24px)更显高端,过大的移动会显得卡顿。
y: 100Pattern 2: Staggered List
模式2:序列列表
Children animate in one after another. The cascade effect that makes lists feel alive.
tsx
const container = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.08,
delayChildren: 0.1,
},
},
}
const item = {
hidden: { opacity: 0, y: 16 },
visible: {
opacity: 1,
y: 0,
transition: { type: "spring", stiffness: 300, damping: 25 },
},
}
<motion.ul variants={container} initial="hidden" animate="visible">
{items.map((i) => (
<motion.li key={i.id} variants={item}>
{i.content}
</motion.li>
))}
</motion.ul>Timing guide:
- 3–5 items:
staggerChildren: 0.1 - 6–12 items:
staggerChildren: 0.06 - 12+ items: (or animate as a group)
staggerChildren: 0.03
Anti-pattern: Don't stagger more than ~15 items individually — it feels slow. Group them or use a wave effect.
子元素依次入场动画。这种级联效果让列表更具活力。
tsx
const container = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.08,
delayChildren: 0.1,
},
},
}
const item = {
hidden: { opacity: 0, y: 16 },
visible: {
opacity: 1,
y: 0,
transition: { type: "spring", stiffness: 300, damping: 25 },
},
}
<motion.ul variants={container} initial="hidden" animate="visible">
{items.map((i) => (
<motion.li key={i.id} variants={item}>
{i.content}
</motion.li>
))}
</motion.ul>时间指南:
- 3–5个元素:
staggerChildren: 0.1 - 6–12个元素:
staggerChildren: 0.06 - 12个以上元素:(或作为一个组执行动画)
staggerChildren: 0.03
反模式:不要单独为超过15个元素设置序列动画——这会让加载显得缓慢。可以将它们分组或使用波浪效果。
Pattern 3: Layout Animations
模式3:布局动画
Automatically animate between layout states. Motion's killer feature.
tsx
<motion.div layout transition={{ type: "spring", stiffness: 300, damping: 25 }}>
{isExpanded ? <ExpandedContent /> : <CollapsedContent />}
</motion.div>自动在不同布局状态间执行动画。这是Motion的核心亮点功能。
tsx
<motion.div layout transition={{ type: "spring", stiffness: 300, damping: 25 }}>
{isExpanded ? <ExpandedContent /> : <CollapsedContent />}
</motion.div>Shared Layout (Tabs, Active Indicators)
共享布局(标签页、激活指示器)
tsx
{tabs.map((tab) => (
<button key={tab.id} onClick={() => setActive(tab.id)}>
{tab.label}
{active === tab.id && (
<motion.div
layoutId="activeTab"
className="absolute inset-0 bg-primary rounded-md"
transition={{ type: "spring", stiffness: 400, damping: 30 }}
/>
)}
</button>
))}When to use: Tab indicators, expanding cards, reordering lists, filtering grids.
Performance tip: Add if you only need to animate position (not size). It's cheaper.
layout="position"tsx
{tabs.map((tab) => (
<button key={tab.id} onClick={() => setActive(tab.id)}>
{tab.label}
{active === tab.id && (
<motion.div
layoutId="activeTab"
className="absolute inset-0 bg-primary rounded-md"
transition={{ type: "spring", stiffness: 400, damping: 30 }}
/>
)}
</button>
))}适用场景:标签页指示器、展开卡片、重排列表、筛选网格。
性能提示:如果只需要动画位置(不需要大小),请添加。这样性能开销更低。
layout="position"Pattern 4: Exit Animations (AnimatePresence)
模式4:退场动画(AnimatePresence)
Elements animate out before being removed from the DOM.
tsx
<AnimatePresence mode="wait">
{isVisible && (
<motion.div
key="modal"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ type: "spring", stiffness: 300, damping: 25 }}
>
Modal content
</motion.div>
)}
</AnimatePresence>元素在从DOM中移除前先执行退场动画。
tsx
<AnimatePresence mode="wait">
{isVisible && (
<motion.div
key="modal"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ type: "spring", stiffness: 300, damping: 25 }}
>
模态框内容
</motion.div>
)}
</AnimatePresence>AnimatePresence modes
AnimatePresence 模式
| Mode | Behavior | Use for |
|---|---|---|
| Enter and exit at the same time | Crossfade effects |
| Wait for exit to finish before entering | Page transitions, modals |
| Exiting element pops out of layout flow | Lists where items are removed |
| 模式 | 行为 | 适用场景 |
|---|---|---|
| 入场和退场动画同时执行 | 交叉淡入效果 |
| 等待退场动画完成后再执行入场动画 | 页面过渡、模态框 |
| 退场元素脱离布局流 | 移除元素的列表 |
Pattern 5: Hover & Tap Micro-interactions
模式5:悬停与点击微交互
tsx
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
transition={{ type: "spring", stiffness: 500, damping: 30 }}
>
Click me
</motion.button>tsx
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
transition={{ type: "spring", stiffness: 500, damping: 30 }}
>
点击我
</motion.button>Hover Patterns by Element Type
按元素类型划分的悬停模式
| Element | whileHover | whileTap |
|---|---|---|
| Button (primary) | | |
| Card | | — |
| Icon button | | |
| Link | | — |
| Avatar | | — |
Anti-pattern: Don't scale buttons more than 1.05 — it looks cartoonish. Subtle (1.01–1.03) feels premium.
| 元素 | whileHover | whileTap |
|---|---|---|
| 按钮(主按钮) | | |
| 卡片 | | — |
| 图标按钮 | | |
| 链接 | | — |
| 头像 | | — |
反模式:不要将按钮的缩放比例设置超过1.05——这会显得卡通化。细微的缩放(1.01–1.03)更显高端。
Pattern 6: Scroll-Triggered Animations
模式6:滚动触发动画
Animate on Scroll Into View
滚动到视图内时执行动画
tsx
<motion.div
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ type: "spring", stiffness: 200, damping: 20 }}
>
Appears when scrolled into view
</motion.div>viewport.once: trueviewport.margintsx
<motion.div
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ type: "spring", stiffness: 200, damping: 20 }}
>
滚动到视图内时出现
</motion.div>viewport.once: trueviewport.marginScroll-Linked Progress
滚动关联进度
tsx
const { scrollYProgress } = useScroll()
const opacity = useTransform(scrollYProgress, [0, 0.3], [1, 0])
const y = useTransform(scrollYProgress, [0, 0.3], [0, -50])
<motion.div style={{ opacity, y }}>
Parallax hero content
</motion.div>tsx
const { scrollYProgress } = useScroll()
const opacity = useTransform(scrollYProgress, [0, 0.3], [1, 0])
const y = useTransform(scrollYProgress, [0, 0.3], [0, -50])
<motion.div style={{ opacity, y }}>
视差首屏内容
</motion.div>Pattern 7: Page Transitions
模式7:页面过渡动画
tsx
// In your layout or page wrapper
<AnimatePresence mode="wait">
<motion.main
key={pathname}
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -8 }}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
>
{children}
</motion.main>
</AnimatePresence>Keep it subtle. Page transitions should be fast (200–300ms feel) and small (8–12px movement). Flashy page transitions feel like 2015.
tsx
// 在布局或页面包装器中
<AnimatePresence mode="wait">
<motion.main
key={pathname}
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -8 }}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
>
{children}
</motion.main>
</AnimatePresence>保持细微:页面过渡动画应快速(200–300ms的感知时长)且移动幅度小(8–12px)。过于花哨的页面过渡动画已经过时了。
Pattern 8: Number/Text Transitions
模式8:数字/文本过渡
tsx
// Animate a counter
<motion.span
key={count}
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
>
{count}
</motion.span>Wrap in for the exit animation. Great for dashboards, pricing, live data.
AnimatePresencetsx
// 动画计数器
<motion.span
key={count}
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
>
{count}
</motion.span>将其包裹在中以实现退场动画。非常适用于仪表板、定价页面和实时数据展示。
AnimatePresenceAnti-Patterns to Avoid
需要避免的反模式
| ❌ Don't | ✅ Do Instead |
|---|---|
Animate | Use |
Large movement values ( | Subtle values ( |
| Bounce on everything | Reserve bounce for playful/celebratory moments |
| Animate on every scroll event | Use |
| Different timing for every element | Use consistent spring configs project-wide |
| Animation on page load for everything | Prioritize above-the-fold; stagger the rest |
| Custom easing curves | Use springs — they respond to interruption better |
| ❌ 不要做 | ✅ 正确做法 |
|---|---|
直接对 | 使用 |
使用过大的移动值(如 | 使用细微的移动值( |
| 所有元素都添加弹跳效果 | 仅在趣味/庆祝场景中使用弹跳效果 |
| 在每次滚动事件时都执行动画 | 使用 |
| 每个元素使用不同的时间配置 | 在整个项目中使用统一的弹簧配置 |
| 页面加载时对所有元素执行动画 | 优先处理首屏内容;其他内容使用序列动画 |
| 使用自定义缓动曲线 | 使用弹簧动画——它们对中断的响应更好 |
Recommended Component Library References
推荐的组件库参考
When building animated components, reference these for patterns and inspiration:
- Magic UI — 150+ animated React components, shadcn/ui compatible
- Motion Primitives — Copy-paste motion components for React
- Aceternity UI — Trendy animated components (heavier, more dramatic)
When a user wants a specific animated component (text reveal, animated border, gradient animation, etc.), check these libraries first — there's likely a battle-tested implementation.
在构建动画组件时,可以参考以下库获取模式和灵感:
- Magic UI — 150+ 个动画React组件,兼容shadcn/ui
- Motion Primitives — 可直接复制使用的React Motion组件
- Aceternity UI — 潮流动画组件(体积较大,效果更夸张)
当用户需要特定的动画组件(如文本渐显、动画边框、渐变动画等)时,请先查看这些库——通常会有经过实战检验的实现方案。
Quick Decision Guide
快速决策指南
| Scenario | Pattern | Spring Config |
|---|---|---|
| Card appearing | Fade + Rise | smooth |
| List loading | Staggered List | smooth, 0.08s stagger |
| Tab switching | Shared Layout ( | snappy |
| Modal open/close | AnimatePresence + scale | smooth |
| Button press | whileHover + whileTap | snappy |
| Landing page sections | Scroll-triggered | gentle |
| Page navigation | Page Transition | smooth |
| Dashboard counter | Number Transition | snappy |
| Notification popup | Fade + Rise + bounce | bouncy |
| Accordion expand | Layout animation | smooth |
| 场景 | 模式 | 弹簧配置 |
|---|---|---|
| 卡片入场 | 淡入+上滑 | smooth |
| 列表加载 | 序列列表 | smooth,0.08s 序列间隔 |
| 标签页切换 | 共享布局( | snappy |
| 模态框打开/关闭 | AnimatePresence + 缩放 | smooth |
| 按钮点击 | whileHover + whileTap | snappy |
| 着陆页区块 | 滚动触发 | gentle |
| 页面导航 | 页面过渡 | smooth |
| 仪表板计数器 | 数字过渡 | snappy |
| 通知弹出 | 淡入+上滑+弹跳 | bouncy |
| 手风琴展开 | 布局动画 | smooth |
Examples
示例
Example 1: "Add animations to this card grid"
示例1:“为这个卡片网格添加动画”
Apply staggered fade+rise entrance to the grid, hover lift effect on each card:
tsx
<motion.div
variants={container}
initial="hidden"
animate="visible"
className="grid grid-cols-3 gap-4"
>
{cards.map((card) => (
<motion.div
key={card.id}
variants={item}
whileHover={{ y: -4, boxShadow: "0 8px 30px rgba(0,0,0,0.12)" }}
transition={{ type: "spring", stiffness: 300, damping: 25 }}
>
<Card {...card} />
</motion.div>
))}
</motion.div>为网格添加序列淡入+上滑入场动画,为每个卡片添加悬停抬起效果:
tsx
<motion.div
variants={container}
initial="hidden"
animate="visible"
className="grid grid-cols-3 gap-4"
>
{cards.map((card) => (
<motion.div
key={card.id}
variants={item}
whileHover={{ y: -4, boxShadow: "0 8px 30px rgba(0,0,0,0.12)" }}
transition={{ type: "spring", stiffness: 300, damping: 25 }}
>
<Card {...card} />
</motion.div>
))}
</motion.div>Example 2: "Make this modal feel better"
示例2:“让这个模态框更有质感”
Wrap in AnimatePresence, add scale + opacity entrance/exit, overlay fade:
tsx
<AnimatePresence>
{isOpen && (
<>
<motion.div
className="fixed inset-0 bg-black/50"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
<motion.div
className="fixed inset-0 flex items-center justify-center"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ type: "spring", stiffness: 300, damping: 25 }}
>
<ModalContent onClose={onClose} />
</motion.div>
</>
)}
</AnimatePresence>用AnimatePresence包裹,添加缩放+透明度的入场/退场动画,以及遮罩层淡入效果:
tsx
<AnimatePresence>
{isOpen && (
<>
<motion.div
className="fixed inset-0 bg-black/50"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
<motion.div
className="fixed inset-0 flex items-center justify-center"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ type: "spring", stiffness: 300, damping: 25 }}
>
<ModalContent onClose={onClose} />
</motion.div>
</>
)}
</AnimatePresence>Example 3: "Add scroll animations to this landing page"
示例3:“为这个着陆页添加滚动动画”
Apply with staggered children to each section:
whileInViewtsx
<motion.section
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ type: "spring", stiffness: 200, damping: 20 }}
>
<h2>Feature Section</h2>
<motion.div
variants={container}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
>
{features.map((f) => (
<motion.div key={f.id} variants={item}>
<FeatureCard {...f} />
</motion.div>
))}
</motion.div>
</motion.section>为每个区块应用并为子元素设置序列动画:
whileInViewtsx
<motion.section
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ type: "spring", stiffness: 200, damping: 20 }}
>
<h2>功能区块</h2>
<motion.div
variants={container}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
>
{features.map((f) => (
<motion.div key={f.id} variants={item}>
<FeatureCard {...f} />
</motion.div>
))}
</motion.div>
</motion.section>