mcpField
M
motion-design-patterns

motion-design-patterns

github.com/dylanfeltus设计与 UI180166 次安装6 周前更新
代码仓库

关于 motion-design-patterns

面向 React 项目的 Motion 动画设计模式技能,提供弹簧、列表交错、布局、滚动触发和页面过渡等实现参考。适合构建或润色界面的开发者,强调动画目的性、性能与全项目的一致性。

主要功能

  1. 01弹簧动画参数参考
  2. 02淡入上移与列表交错
  3. 03布局动画和共享布局
  4. 04悬停与按压交互
  5. 05滚动触发与页面过渡

适用场景

  1. 01为卡片网格添加入场和悬停效果
  2. 02实现标签切换或手风琴展开动画
  3. 03为落地页添加滚动触发效果

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dylanfeltus/skills --skill motion-design-patterns

安装后在 Claude Code 中输入/motion-design-patterns确认已加载。

附带文件README.md

详细介绍

动效设计模式

适用于 React 的 Framer Motion(Motion)模式——弹簧动画、交错动画、布局动画、微交互、滚动触发效果和退出动画。这是区分普通 UI 与精致 UI 的首要因素。

适用场景

  • 在 React 项目中构建或改进 UI 动画
  • 用户提出“精致感”“惊喜感”“微交互”或“让它用起来更舒服”等需求
  • 添加入场/退出动画、悬停效果或页面过渡
  • 让列表、卡片、模态框或导航更显高级
  • 用户提到 Magic UI、Motion Primitives 或 Framer Motion

核心理念

  • 动效应有明确目的。 每个动画都应传达某种信息——状态变化、层级、空间关系或反馈。
  • 少即是多。 一个调校得当的弹簧动画胜过五个相互冲突的动画。
  • 性能优先。 只为 transform 和 opacity 添加动画。切勿为 width、height、top、left 或 margin 添加动画。
  • 保持一致很重要。 在整个项目中使用相同的弹簧配置。

依赖项

npm install motion

导入:import { motion, AnimatePresence, stagger, useScroll, useTransform } from "motion/react"

注意:该软件包于 2024 年末从 framer-motion 更名为 motion。两者都可用,但目前的软件包名称是 motion。


弹簧配置

弹簧动画比缓动曲线更自然。可将以下配置用作默认值:

推荐默认值
// 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 }
快速参考
感觉stiffnessdamping适用场景
干脆50030按钮、切换开关、标签
平滑30025卡片、面板、模态框
柔和20020页面过渡、首屏区块
弹跳40015通知、徽章、有趣的 UI
经验法则
  • stiffness 越高 = 动画越快
  • damping 越低 = 弹跳越明显
  • damping ratio < 1 = 会过冲(弹跳)
  • 如需无弹跳,将 damping 设为 ≥ 2 × √stiffness

模式 1:淡入 + 上移

最常用的入场动画。元素淡入的同时略微向上滑动。

<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ type: "spring", stiffness: 300, damping: 25 }}
>
  Content here
</motion.div>

适用场景: 卡片、区块,以及页面上出现的任何内容。

反模式: 不要使用 y: 100 或较大的值——微小的位移(12–24px)更显高级,较大的位移则会显得生硬。


模式 2:列表交错动画

子元素依次进入。级联效果能让列表显得鲜活。

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 个项目逐个交错出现——这样会显得缓慢。可将它们分组,或使用波浪效果。


模式 3:布局动画

在布局状态之间自动添加动画。这是 Motion 的杀手级特性。

<motion.div layout transition={{ type: "spring", stiffness: 300, damping: 25 }}>
  {isExpanded ? <ExpandedContent /> : <CollapsedContent />}
</motion.div>
共享布局(标签页、活动指示器)
{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"。这样开销更低。


模式 4:退出动画(AnimatePresence)

元素从 DOM 中移除前先播放退出动画。

<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>
AnimatePresence 模式
模式行为适用场景
"sync"(默认)进入和退出同时发生交叉淡化效果
"wait"等退出动画结束后再进入页面过渡、模态框
"popLayout"退出的元素脱离布局流删除项目时的列表

模式 5:悬停和轻触微交互

<motion.button
  whileHover={{ scale: 1.02 }}
  whileTap={{ scale: 0.98 }}
  transition={{ type: "spring", stiffness: 500, damping: 30 }}
>
  Click me
</motion.button>
不同元素类型的悬停模式
元素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)更显高级。


模式 6:滚动触发动画

滚动进入视口时播放动画
<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 — 仅在首次进入视口时播放动画(最常用于落地页)。 viewport.margin — 负边距会提前触发动画(在元素完全进入视口之前)。

与滚动关联的进度
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>

模式 7:页面过渡

// 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>

保持微妙。 页面过渡应该快速(时长 200–300ms),移动幅度也要小(8–12px)。花哨的页面过渡会让人感觉像是 2015 年的产物。


模式 8:数字/文本过渡

// 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>

将内容包裹在 AnimatePresence 中,以实现退出动画。非常适合仪表盘、定价和实时数据。


应避免的反模式

❌ 不要✅ 应该
直接为 width/height 添加动画使用 scale 或 layout 动画
使用较大的移动值(y: 200)使用微妙的值(y: 16–24)
所有元素都添加弹跳效果仅在活泼或庆祝的场景中使用弹跳效果
每次滚动事件都触发动画使用带有 once: true 的 whileInView
每个元素都使用不同的时间配置在整个项目中使用一致的弹簧配置
页面加载时为所有内容添加动画优先处理首屏内容;其余内容使用错开动画
自定义缓动曲线使用弹簧——它们更能适应中断

推荐的组件库参考

构建带动画的组件时,可参考以下库中的模式和灵感:

  • Magic UI — 150 多个带动画的 React 组件,兼容 shadcn/ui
  • Motion Primitives — 可复制粘贴的 React 动效组件
  • Aceternity UI — 时髦的带动画组件(更重、更夸张)

当用户需要某个特定的带动画组件(文本显现、动画边框、渐变动画等)时,先查看这些组件库——其中很可能有经过实践检验的实现方案。


快速决策指南

场景模式弹簧配置
卡片出现淡入 + 上移smooth
列表加载错开列表smooth,错开 0.08s
切换标签页共享布局(layoutId)snappy
打开/关闭模态框AnimatePresence + 缩放smooth
按钮按下whileHover + whileTapsnappy
落地页版块滚动触发gentle
页面导航页面过渡smooth
仪表盘计数器数字过渡snappy
通知弹窗淡入 + 上移 + 弹跳bouncy
手风琴展开布局动画smooth

示例

示例 1:“为这个卡片网格添加动画”

为网格应用错开的淡入上移动画,并为每张卡片添加悬停上移效果:

<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>
示例 2:“让这个模态框的效果更好”

使用 AnimatePresence 包裹,添加缩放和不透明度的进入/退出动画,并让遮罩淡入:

<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>
示例 3:“为这个落地页添加滚动动画”

为每个版块应用 whileInView,并让子元素错开播放动画:

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

评论

支持 Markdown