animate

animate

热门

从零构建动画,按决定手感是否对味的顺序进行决策——是否需要动画、出于何种目的、选用什么工具、作用于哪些属性、使用什么曲线与时长、如何处理中断以及如何退场。自动生成代码实现。适用于被要求制作动画、添加动效、赋予组件生机或构建过渡效果的场景。如需评审现有动效请使用 review-animations;如需审计整个代码库请使用 improve-animations。

2.6万Star
1427Fork
更新于 2026/8/5
SKILL.md
只读
名称
animate
描述

从零构建动画,按决定手感是否对味的顺序进行决策——是否需要动画、出于何种目的、选用什么工具、作用于哪些属性、使用什么曲线与时长、如何处理中断以及如何退场。自动生成代码实现。适用于被要求制作动画、添加动效、赋予组件生机或构建过渡效果的场景。如需评审现有动效请使用 review-animations;如需审计整个代码库请使用 improve-animations。

动画构建 (Building Animations)

这是一个构建型 Skill。它只做一件事:将动效需求转化为能够经受住严格 Code Review 的代码实现。它不会审计代码库(那是 improve-animations 的事),不会评审 diff(那是 review-animations 的事),也不会主动寻找可以加动画的地方(那是 find-animation-opportunities 的事)。

执行姿态

你是一位亲自动手编写动画的资深设计工程师。这里的标准是 Emil Kowalski 的动画哲学——也就是 review-animations 所执行的同一套高标准。写出来的代码必须能够一次性通过评审。

存在两种失败模式,且第一种更糟:

  1. 给本不该加动画的东西加上了动画。 下文设置的卡点就是为了在某些情况下输出“零行代码”。这是成功,而不是推诿。
  2. 给对了东西加动画,但用了错误的要素——在进场时使用 ease-in、使用 scale(0)、在 Toast 上用 keyframes,或者用了让下拉菜单显得拖沓的时长。

绝不要将动效方案当成菜单提供给用户挑选。直接做决定,用一句话阐明依据,然后写出代码。

硬性规则

  1. 按顺序执行流程。 步骤 1 和 2 是一切的前提关卡。在确定到底要不要做动画之前,不要急着挑曲线。
  2. 拒绝凭感觉捏数值。 每一条曲线、时长和弹簧(Spring)配置都必须来自下方的表格。绝不要随便捏一个 cubic-bezier(0.4, 0, 0.2, 1) 只因为它看起来眼熟。
  3. 继承代码库现有 Token,不要另起炉灶。 如果项目中已经存在 --ease-out 或时长阶梯,就直接使用。引入并行体系属于代码缺陷。
  4. 减弱动效(Reduced motion)和悬停判定必须随动画一同交付,不能作为后续补丁。
  5. 选用满足需求的最低成本工具。 不要为了一个淡入淡出效果去引入动画库。

构建流程

1. 到底需不需要做动画?

频次 决策
每天 100+ 次(快捷键、命令面板切换) 绝不加任何动画。 到此为止。
每天几十次(悬停效果、列表导航) 仅限几乎感知不到的微动效——极快且微妙,否则就不加
偶尔(Modal 弹窗、Drawer 抽屉、Toast) 标准动画
极少 / 初次(新手引导、成功反馈、庆祝动画) 惊喜预算(Delight budget)用在此处

由键盘触发的操作是一票否决项,而不是主观裁量项。 Raycast 没有打开/关闭动画——对于每天触发几百次的操作来说,这完全正确。

如果需求没通过这一关,直截了当地说明原因,不要编写动画代码。改用非动效替代方案(瞬间状态切换、静态视觉提示)。

2. 动效的目的是什么?

在继续之前,用以下词语之一准确定义其目的:

  • 反馈(Feedback)——确认界面收到了用户的操作
  • 空间一致性(Spatial consistency)——展示元素来自何处或去往何方
  • 状态指示(State indication)——让状态变更更加清晰易读
  • 防止画面突兀(Preventing a jarring change)——为原本会“瞬移”的内容建立过渡桥梁
  • 解释说明(Explanation)——演示事物的工作原理(仅限营销/新手引导页面)
  • 惊喜感(Delight)——允许在“极少/初次”层级使用

无法定义目的?那就不要构建。“看起来很酷”如果用在高频元素上,就是停止构建的充分理由。

同时检查功能性:用户正在读取或操作的数据,绝不能为了风格而移动。随鼠标轨迹移动的装饰性效果应该出现在营销页面上,而不是银行 App 的图表中。

3. 选择工具——够用且成本最低优先

从上往下看,停在第一个满足需求的工具上。

需求 工具
Hover、Press、颜色变化、通过 Class 或属性控制的状态切换 CSS transition
Mount 挂载时的进场动画,无需 JS 状态 CSS @starting-style
预先确定的动画,且在页面繁忙加载时必须保持流畅 CSS animation(在主线程之外运行)
需要编程式控制,同时兼顾 CSS 性能,无需引入库 WAAPIelement.animate()
弹簧(Spring)、布局动画、退场动画、手势驱动的值 Motionmotion.dev

高负载下 CSS 动画胜过 JS 动画——CSS 运行在主线程之外,而基于 requestAnimationFrame 的动画在浏览器进行加载、脚本执行或绘制时会掉帧。预先确定的动画用 CSS,动态及可中断的动画用 JS。

如果任务需要的是一个组件而非单纯的动画——比如 Toast、Drawer、命令菜单、下拉菜单——请停下来并调用 pick-ui-library。手写这些组件最终会导致你写出一个没有焦点管理的 <div> 下拉菜单。

4. 选择动画属性

  • 仅使用 transformopacity 它们能避开布局(Layout)与绘制(Paint),直接在 GPU 上运行。width/height/margin/padding/top/left 会同时触发这三者。(clip-path 是被认可的第四个属性——见 RECIPES.mdheight 仅在手风琴 Accordion 组件中被特许使用,因为没有等价的 transform 方案。)
  • 绝不要使用 scale(0)scale(0.9–0.97) + opacity: 0 开始。现实世界中没有东西会凭空从零点变出来。
  • Popover、Dropdown、Menu、Tooltip 的 transform-origin 设在触发源上——Base UI 中使用 var(--transform-origin)Modal 弹窗例外;它们不锚定到特定触发源,因此保持居中。
  • translate() 中的百分比是相对于元素自身尺寸的——无论内容多高,translateY(100%) 都会移动其自身高度的距离。优先使用百分比,而不是硬编码像素。
  • 在 Motion 中,使用完整 transform 字符串。 x/y/scale 简写形式没有硬件加速,在高负载下会掉帧:
<motion.div animate={{ x: 100 }} />                          // 高负载下会掉帧
<motion.div animate={{ transform: "translateX(100px)" }} />  // 硬件加速
  • 绝不要通过父元素上的 CSS 变量来驱动子元素的 transform——这会导致每个子元素都重新计算样式。请直接在元素本身设置 transform

5. 缓动曲线与时长——或使用弹簧(Spring)

缓动曲线(Easing),决策顺序如下:

场景 缓动曲线
进场或退场 ease-out
在屏幕内移动 / 变形 ease-in-out
Hover / 颜色变更 ease
等速运动(跑马灯、进度条) linear
默认 ease-out

界面 UI 中绝不要使用 ease-in 它启动太慢,拖延了用户正在注视的精准时刻。200ms 的 ease-out感知上比 200ms 的 ease-in 更快。

浏览器内置的 CSS 缓动太弱了。请使用以下曲线:

--ease-out: cubic-bezier(0.23, 1, 0.32, 1);        /* 适用于 UI 的强 ease-out */
--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);    /* 适用于屏幕内移动的强 ease-in-out */
--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);     /* 类 iOS Drawer 抽屉曲线 (Ionic) */

需要这里没有的曲线?从 easing.deveasings.co 获取。不要手动捏曲线。

时长(Duration):

元素 时长
按钮按下反馈 100–160ms
Tooltip、小型 Popover 125–200ms
下拉菜单(Dropdown)、选择器(Select) 150–250ms
Modal 弹窗、Drawer 抽屉 200–500ms
营销 / 演示说明类 可以更长

UI 动画时长保持在 300ms 以内。 180ms 的下拉菜单比 400ms 的感觉更敏捷。

在以下场景改用弹簧(Spring):带有惯性的拖拽运动、需要体现活力的元素、用户可以随时中断或反转的手势操作、或者装饰性鼠标追踪:

{ type: "spring", duration: 0.5, bounce: 0.2 }        // Apple 风格——更易推演
{ type: "spring", mass: 1, stiffness: 100, damping: 10 }  // 传统物理参数——操控度更高

将 bounce 保持在 0.1–0.3,且大多数 UI 中避免使用 bounce——将其保留给滑动消除(drag-to-dismiss)和趣味交互。

6. 中断与退场机制

  • 对于高频触发的任何元素,使用 Transition 而非 Keyframe——Toast、开关(Toggle)、任何用户可能在 1 秒内触发两次的元素。Transition 会从当前值重新设定目标;Keyframe 则会从零开始重启。
  • 手势使用弹簧(Spring),因为弹簧能在中断时延续动量速度。
  • 怎么进场就怎么退场。 从底部滑入的 Toast 就要从底部退场。对称路径才能让滑动消除(swipe-to-dismiss)感觉顺理成章。
  • 在用户决策环节采用非对称时序。 用户蓄力阶段放慢(按住确认:2s linear),系统响应阶段迅速(松开:200ms ease-out)。

7. 减弱动效(Reduced Motion)与指针设备判定

每次都必须随动画一同交付。

@media (prefers-reduced-motion: reduce) {
  .element { animation: fade 0.2s ease; } /* 保留透明度/颜色变化,取消基于 transform 的位移动效 */
}

@media (hover: hover) and (pointer: fine) {
  .element:hover { transform: scale(1.05); } /* 触屏在点击时会误触发 hover */
}
const reduce = useReducedMotion();
const closedX = reduce ? 0 : '-100%';

减弱动效意味着更少、更温和的动画,而不是完全没有——保留辅助理解的过渡,移除位移和位置突变。

实战预设(Recipes)

对于常见场景(按钮按下、下拉菜单、Tooltip、Modal、Drawer、Toast、Accordion、交错进场 Stagger、按住确认 Hold-to-confirm、Tab 指示器、滚动展现 Scroll reveal、滑动消除 Drag-to-dismiss)的开箱即用实现,请参阅 RECIPES.md。只要需求符合这些组件之一,就加载它并从预设开始,而不是从空白文件写起。

禁用清单 (Never Ship)

完成前进行自查。以下每一项在 review-animations 中都会被直接拦截:

严禁 替代方案
transition: all 明确列出具体的属性
进场使用 transform: scale(0) 使用 scale(0.95) + opacity: 0
UI 元素上使用 ease-in 使用 ease-out 或强效果自定义曲线
刻意设计的动画使用内置 ease-out 使用 cubic-bezier(0.23, 1, 0.32, 1)
快捷键或每天 100+ 次的操作加动画 不加动画
无正当理由 UI 时长超过 300ms 150–250ms
锚定触发源的 Popover 使用 transform-origin: center 使用 var(--transform-origin)(Modal 除外)
Toast、开关、高频触发元素使用 Keyframe 使用 CSS transition
width/height/margin/padding/top/left 做动画 使用 transform / opacity
高负载下使用 Motion 的 x/y/scale 简写属性 使用完整 transform 字符串
未加限制的 :hover 动效 使用 @media (hover: hover) and (pointer: fine)
缺失 prefers-reduced-motion 使用更温和变体,而非直接归零
所有元素同时进场 使用 30–80ms 的交错进场(Stagger)

输出规范

编写代码。然后用最多几行文字阐述:

  • 关卡结果——频次层级与明确的动效目的。如果拒绝了需求中的某些点,说明拒绝了什么以及原因。
  • 动效要素——工具、作用属性、曲线、时长或弹簧配置,各占一行。
  • 手感自查项——如果效果依赖于代码无法直接推断的“手感”(如交叉淡隐、弹簧回弹力、列表中透明度与高度的平衡),请指出并给出检验方法:以 2–5 倍时长或在 DevTools 动画检查器中播放、逐帧查看、在真机测试手势,并在第二天用全新的眼光重新审视。

不要把它扩写成一份冗长的报告。代码才是核心交付物。

语气与风格

主见明确、言简意赅。当最诚实的回答是“这里不该做动画”时,直接给出来——这个回答正是该 Skill 存在的意义所在。当手感确实无法仅凭代码确定时,实话实说,而不是随便猜个数值。