mcpField
M
motion

motion

github.com/paragonhq设计与 UI044 次安装5 周前更新
代码仓库

关于 motion

用于选择界面动效的设计技能,涵盖时长、属性、缓动、无障碍与性能指导。面向产品界面构建者,帮助判断动画是否有用,以及如何用 CSS、Motion 或 GSAP 实现。

主要功能

  1. 01按使用频率提供动效指导
  2. 02动画目的与时长规则
  3. 03CSS、Motion 和 GSAP 的选择建议
  4. 04属性与缓动建议
  5. 05减少动态效果的指导
  6. 06实现范例与反模式

适用场景

  1. 01为菜单和弹出层选择动效
  2. 02设置对话框和 Toast 的过渡
  3. 03为布局变化和标签指示器添加动画
  4. 04让界面动效遵循减少动态效果设置

安装

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

$npx skills add https://github.com/paragonhq/stealth-skills --skill motion

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

详细介绍

动效

动效是界面设计中最容易被滥用的工具,因为它最显眼。这项技能旨在让你减少动效,并确保保留下来的动效正确无误。

开始编写前

按顺序回答以下问题。每个关卡都可能让你不需要写任何代码就结束任务,而这正是好结果。

1. 用户会多频繁地看到它?
看到的频率规则
每隔几秒(输入、列表导航、悬停在行上)不使用动效,或使用时长低于 120ms、几乎不可见的动效
每天多次(菜单、调色板、选项卡、切换开关)只提供快速反馈,时长为 120–200ms,不加多余效果
每天几次(对话框、抽屉、底部弹层、提示消息)完整的进入和退出动效,时长为 180–320ms
很少(引导流程、成功提示、首次运行)只有这里适合使用时长更长、表现力更强的动画

由键盘触发的操作应立即响应。 ⌘K、Escape、j/k、使用快捷键切换选项卡:在同一帧渲染新状态。此处的动效会让人觉得响应迟缓。

2. 动效的目的是什么?

选一个。如果选不出来,就停下。

  • 确认 — 按下、切换、拖放操作。让用户知道界面已收到操作。
  • 定位 — 展示元素从哪里来或到哪里去。弹出层从其触发器展开;提示消息沿着来时的方向离开。
  • 衔接 — 连接两种布局,避免内容突然跳变。重新排序、展开、选项卡指示器。
  • 提示 — 在用户未查看时,状态发生了变化。已保存徽标、数字滚动。
  • 解释 — 展示某个功能的工作方式。仅用于营销和引导流程。

“看起来更有活力”不是目的。“其他内容都有动画”也不是目的。

3. 用哪种工具?

按以下顺序选择能完成任务的最简单工具:

需求使用
悬停、聚焦、按下、颜色、简单的进入效果CSS transition
循环或多步动画,无需中断CSS @keyframes
已挂载 React 元素的进入和退出、布局变化、弹簧动画、中断Motion(motion/react)
时间轴、拖动进度、固定定位、拆分文本、按顺序播放的场景GSAP
与滚动关联优先使用 CSS 滚动驱动动画;无法满足时再用 ScrollTrigger

不要为了淡入淡出安装库。项目中已有 Motion 的 AnimatePresence 时,不要用 setTimeout 手动实现退出动画。

4. 使用哪些属性?

transform 和 opacity。这两者都由合成器处理;其他属性则无法保证。

目标应该不应该
出现opacity 0→1 + scale .96→1 或 translateY 6px→0scale(0)、仅切换 display
展开至内容高度测量并对 height 做动画,同时使用 overflow: hidden;或使用 grid-template-rows: 0fr→1fr对 max-height: 9999px 做动画
沿边缘显示clip-path: inset(...)width
模糊淡入将 filter: blur(6px→0) 与透明度搭配,时长要短只用模糊效果,或对大面积内容应用模糊
在位置之间移动通过布局动画使用 transform(Motion 中使用 layout,GSAP 中使用 FLIP)对 top/left 做动画
颜色使用 transition 过渡 background-color、color、border-colorfilter: hue-rotate

绝不要使用 transition: all。逐一列出属性。这样更快,也能避免意外。

5. 使用哪种曲线?
情况曲线
进入屏幕强缓出:cubic-bezier(0.16, 1, 0.3, 1)
离开屏幕仅此处可以使用缓入,或以 70% 时长使用相同的缓出曲线
在屏幕内移动(重新排序、选项卡指示器、调整大小)缓入缓出:cubic-bezier(0.76, 0, 0.24, 1)
悬停和颜色变化ease 或柔和的缓出,绝不用 expo
连续动画(跑马灯、旋转指示器、进度)linear
跟随手势或指针使用弹簧,而不是曲线
iOS 风格的底部弹层或抽屉cubic-bezier(0.32, 0.72, 0, 1)

内置的 ease-in-out 和 ease-out 太弱,不足以营造明确的效果。请使用上面列出的自定义曲线,并将它们存储为设计令牌:--ease-out-expo、--ease-in-out-quart。

进入动画绝不要使用缓入。用户最关注的是最初的 50ms;缓入会浪费这段时间。

6. 多久?
元素时长
按下反馈100–140ms
悬停状态120–180ms
工具提示、小型弹出层140–200ms
菜单、下拉菜单、选择器160–220ms
标签指示器、切换滑块180–260ms
对话框、抽屉220–320ms
Toast 进入240–320ms;退出 180ms
页面或视图切换300–450ms
营销内容显现600–1200ms,仅一次

退出动画的时长约为进入动画的 70%。低于 100ms 就难以察觉;在产品 UI 中超过 400ms 就会让人等待。拿不准时,就缩短 20%。

7. 弹簧

适用于用户拖动、抛掷的元素,或必须具有真实物理感的元素:

感觉动效配置
灵敏的 UI(指示器、标签、小幅移动){ type: "spring", stiffness: 520, damping: 38, mass: 0.7 }
柔和(卡片、面板、列表重排){ type: "spring", stiffness: 260, damping: 28, mass: 0.9 }
跟随指针(滑动的悬停高亮){ type: "spring", stiffness: 650, damping: 45, mass: 0.5 }
松手后抽屉回弹到位{ type: "spring", stiffness: 300, damping: 30 } 或使用 bounce: 0 和 duration: 0.4

在产品 UI 中,几乎不该使用弹跳。弹簧最多只应过冲一两个像素;如果出现摇晃,就提高阻尼。

8. 从哪里开始?

将 transform-origin 设为触发元素的位置。位于右上角按钮旁的弹出层,应从其右上角展开。对话框是例外:它们居中,并从中心缩放。Toast 从它们将要离开的那一侧进入。

9. 交错

仅对同时出现的列表使用交错,且只出现一次。项目之间间隔 12–25ms,设定上限:第八个项目之后不再增加延迟,否则尾部项目出现时,用户已经开始阅读。重新渲染或筛选条件更改时,绝不使用交错。

10. 中断

如果菜单还在打开时用户将其关闭,菜单应从当前位置反向运动,而不是先完成打开再关闭。CSS 过渡默认会如此处理,关键帧不会,Motion 会。如果你对可中断的动画使用了关键帧,那就是选错了工具。

11. 退出

退出动画应经过精心设计:时长更短、效果更简单,通常只改变不透明度。对话框可以淡入并缩放进入,但退出时只需淡出。用户关闭后,不应有任何内容哪怕只显示一帧,然后才开始消失;点击后的第一帧就应已经发生变化。

12. 减少动态效果

在同一次改动中一并支持:

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

在 Motion 中,使用 useReducedMotion() 并将变换替换为不透明度动画。平滑滚动库应完全禁用。所有原本会动效进入的内容都应直接显示。

方案

菜单 / 弹出层
.menu {
  transform-origin: var(--origin, top left);
  transition: opacity 160ms var(--ease-out-expo), transform 160ms var(--ease-out-expo);
}
.menu[data-state="closed"] { opacity: 0; transform: scale(0.96) translateY(-2px); }
.menu[data-state="open"] { opacity: 1; transform: none; }
对话框
<motion.div
  initial={{ opacity: 0, scale: 0.97, y: 6 }}
  animate={{ opacity: 1, scale: 1, y: 0 }}
  exit={{ opacity: 0, transition: { duration: 0.15 } }}
  transition={{ duration: 0.26, ease: [0.16, 1, 0.3, 1] }}
/>

背景遮罩单独淡入淡出,进入时稍长,退出时稍短。

Toast

从所在边缘进入,采用缓出效果,时长约为 280ms。较早出现的 Toast 堆叠在后方,并使用 scale(0.95) 和 translateY(-8px),同时降低亮度。退出时朝同一边缘移动,时长约为 180ms。悬停时以及标签页隐藏时,暂停计时器。

高度适应内容
.grid { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 240ms var(--ease-in-out-quart); }
.grid[data-open] { grid-template-rows: 1fr; }
.grid > * { overflow: hidden; min-height: 0; }
标签指示器

使用一个带有 layoutId 的元素(Motion),通过弹簧效果在标签之间移动;绝不要为每个标签单独动画 left / width。

在行之间滑动的悬停高亮

使用一个绝对定位的胶囊形元素,在 pointerenter 时更新位置,并通过“跟随指针”弹簧效果进行动画。pointerleave 时,以 150ms 淡出并移除。

数字变化

让数字逐位滚动,而不是让整个数字淡入淡出。使用能处理数字列的库(NumberFlow),不要自己编写。

文本显现(仅用于营销内容)

将文本拆分为单词,设置 opacity 0→1、y 6→0、filter blur(6px→0),单词之间间隔 30ms,仅在首次查看时播放一次。

反模式

在生成的 UI 中看到的做法应改为
transition: all 0.3s ease指定属性,时长为 160–220ms,并使用自定义 ease-out
在图标上使用 animate-bounce不做动画
每张卡片悬停时都上移 -4px 并加上阴影选择其中一种:边框变亮,或背景提亮一级
时长为 1.5s 的骨架屏闪烁效果静态骨架屏,或缓慢的 2.5s 脉冲效果
页面加载时为每个元素添加入场动画最多为标题和一个区域添加一次
所有情况都使用旋转加载图标内容使用骨架屏,只有操作时才使用旋转加载图标
下拉菜单的 duration: 0.50.18
产品 UI 中使用视差效果绝不使用

性能

仅对即将播放动画的元素设置 will-change: transform,并在动画结束后移除。避免为任何带有 backdrop-filter 的元素添加动画。大范围模糊效果开销较高;半径应小于 8px,作用区域也要小。在 CPU 限速的情况下测试:如果帧率下降,应删掉动画属性,而不是缩短时长。

参考资料

评论

支持 Markdown