motion-design-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Motion 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
    transform
    and
    opacity
    only. Never animate
    width
    ,
    height
    ,
    top
    ,
    left
    , or
    margin
    .
  • Consistency matters. Use the same spring configs throughout a project.
  • 动画应具备目的性:每一个动画都要传递特定信息——状态变化、层级关系、空间关联或操作反馈。
  • 少即是多:一个调校得当的弹簧动画效果远胜于五个相互竞争的动画。
  • 性能优先:仅对
    transform
    opacity
    属性执行动画。绝不要对
    width
    height
    top
    left
    margin
    执行动画。
  • 保持一致性:在整个项目中使用统一的弹簧配置。

Dependencies

依赖安装

bash
npm install motion
Import:
import { motion, AnimatePresence, stagger, useScroll, useTransform } from "motion/react"
Note: The package was renamed from
framer-motion
to
motion
in late 2024. Both work, but
motion
is the current package.

bash
npm install motion
导入方式:
import { motion, AnimatePresence, stagger, useScroll, useTransform } from "motion/react"
注意:该包在2024年末从
framer-motion
更名为
motion
。两个包都可以使用,但
motion
是当前的官方包。

Spring 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

速查表

FeelstiffnessdampingUse for
Snappy50030Buttons, toggles, chips
Smooth30025Cards, panels, modals
Gentle20020Page transitions, heroes
Bouncy40015Notifications, badges, fun UI
质感刚度阻尼适用场景
灵动型50030按钮、开关、芯片组件
流畅型30025卡片、面板、模态框
柔和型20020页面过渡、首屏区域
弹跳型40015通知、徽章、趣味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
y: 100
or large values — subtle (12–24px) feels premium, large feels janky.

这是最基础的入场动画。元素在淡入的同时轻微向上滑动。
tsx
<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ type: "spring", stiffness: 300, damping: 25 }}
>
  内容区域
</motion.div>
适用场景:卡片、区块、页面中出现的任意内容。
反模式:不要使用
y: 100
或过大的数值——细微的移动(12–24px)更显高端,过大的移动会显得卡顿。

Pattern 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:
    staggerChildren: 0.03
    (or animate as a group)
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
layout="position"
if you only need to animate position (not size). It's cheaper.

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 模式

ModeBehaviorUse for
"sync"
(default)
Enter and exit at the same timeCrossfade effects
"wait"
Wait for exit to finish before enteringPage transitions, modals
"popLayout"
Exiting element pops out of layout flowLists where items are removed

模式行为适用场景
"sync"
(默认)
入场和退场动画同时执行交叉淡入效果
"wait"
等待退场动画完成后再执行入场动画页面过渡、模态框
"popLayout"
退场元素脱离布局流移除元素的列表

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

按元素类型划分的悬停模式

ElementwhileHoverwhileTap
Button (primary)
{ scale: 1.02 }
{ scale: 0.98 }
Card
{ y: -4, boxShadow: "0 8px 30px rgba(0,0,0,0.12)" }
Icon button
{ scale: 1.1 }
{ scale: 0.9 }
Link
{ x: 2 }
Avatar
{ scale: 1.05 }
Anti-pattern: Don't scale buttons more than 1.05 — it looks cartoonish. Subtle (1.01–1.03) feels premium.

元素whileHoverwhileTap
按钮(主按钮)
{ scale: 1.02 }
{ scale: 0.98 }
卡片
{ y: -4, boxShadow: "0 8px 30px rgba(0,0,0,0.12)" }
图标按钮
{ scale: 1.1 }
{ scale: 0.9 }
链接
{ x: 2 }
头像
{ scale: 1.05 }
反模式:不要将按钮的缩放比例设置超过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: true
— Only animate the first time (most common for landing pages).
viewport.margin
— Negative margin triggers earlier (before element is fully visible).
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 }}
>
  滚动到视图内时出现
</motion.div>
viewport.once: true
— 仅在第一次触发动画(适用于着陆页的常见场景)。
viewport.margin
— 负边距会提前触发动画(在元素完全可见之前)。

Scroll-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
AnimatePresence
for the exit animation. Great for dashboards, pricing, live data.

tsx
// 动画计数器
<motion.span
  key={count}
  initial={{ opacity: 0, y: -10 }}
  animate={{ opacity: 1, y: 0 }}
  exit={{ opacity: 0, y: 10 }}
>
  {count}
</motion.span>
将其包裹在
AnimatePresence
中以实现退场动画。非常适用于仪表板、定价页面和实时数据展示。

Anti-Patterns to Avoid

需要避免的反模式

❌ Don't✅ Do Instead
Animate
width
/
height
directly
Use
scale
or
layout
animations
Large movement values (
y: 200
)
Subtle values (
y: 16–24
)
Bounce on everythingReserve bounce for playful/celebratory moments
Animate on every scroll eventUse
whileInView
with
once: true
Different timing for every elementUse consistent spring configs project-wide
Animation on page load for everythingPrioritize above-the-fold; stagger the rest
Custom easing curvesUse springs — they respond to interruption better

❌ 不要做✅ 正确做法
直接对
width
/
height
执行动画
使用
scale
layout
动画
使用过大的移动值(如
y: 200
使用细微的移动值(
y: 16–24
所有元素都添加弹跳效果仅在趣味/庆祝场景中使用弹跳效果
在每次滚动事件时都执行动画使用
whileInView
并设置
once: true
每个元素使用不同的时间配置在整个项目中使用统一的弹簧配置
页面加载时对所有元素执行动画优先处理首屏内容;其他内容使用序列动画
使用自定义缓动曲线使用弹簧动画——它们对中断的响应更好

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

快速决策指南

ScenarioPatternSpring Config
Card appearingFade + Risesmooth
List loadingStaggered Listsmooth, 0.08s stagger
Tab switchingShared Layout (
layoutId
)
snappy
Modal open/closeAnimatePresence + scalesmooth
Button presswhileHover + whileTapsnappy
Landing page sectionsScroll-triggeredgentle
Page navigationPage Transitionsmooth
Dashboard counterNumber Transitionsnappy
Notification popupFade + Rise + bouncebouncy
Accordion expandLayout animationsmooth
场景模式弹簧配置
卡片入场淡入+上滑smooth
列表加载序列列表smooth,0.08s 序列间隔
标签页切换共享布局(
layoutId
snappy
模态框打开/关闭AnimatePresence + 缩放smooth
按钮点击whileHover + whileTapsnappy
着陆页区块滚动触发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
whileInView
with staggered children to each section:
tsx
<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>
为每个区块应用
whileInView
并为子元素设置序列动画:
tsx
<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>