关于 animate
面向设计与编码 Agent 的界面动画构建技能,按步骤判断是否需要动画及如何实现。它强调沿用现有动效规范、无障碍处理和选择足够简单的实现方式。
主要功能
- 01评估是否适合添加动画
- 02按需求选择实现工具
- 03遵循属性与时长规则
- 04处理减少动态效果和悬停
- 05检查中断与退出行为
适用场景
- 01判断高频操作是否应添加动画
- 02实现菜单、模态框或提示的动效
- 03添加动效并兼顾减少动态效果偏好
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ nexu-io/ open-design --skill animate安装后在 Claude Code 中输入/animate确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
LICENSESOURCES.mdreferences/RECIPES.md详细介绍
构建动画
Open Design 执行上下文
将其用作适用于用户当前界面或已确认需求的功能助手。遵循宿主的任务路由、当前阶段、工具可用性和输出约定。规划描述动效;仅在宿主的创作阶段进行实现。如果宿主不允许额外的审核、录制、渲染或审批阶段,就不要添加这些阶段。复用项目现有的技术栈和动效令牌。配套技能是可选的:仅在已安装时使用;否则,请直接应用此处的相关指导,不要虚构工具调用。仅报告实际观察到的测量值;明确标注视觉估算和无法执行的检查。
此助手用于实现界面动效;review-animations 用于审核现有动效,emilkowalski-motion 提供精简的后续润色工作流。立即处理当前请求;不要停留在问候上。
工作原则
你是一名亲自构建动画的资深设计工程师。标准是 Emil Kowalski 的动画理念,与 review-animations 执行的标准相同。编写的动画应一次通过审核。
有两种失败模式,第一种更糟:
- 为不该动的元素添加动画。 以下关卡的目的有时是让代码一行不写。这是成功,不是推脱。
- 为合适的元素选错动效要素——进入动画使用
ease-in、scale(0)、Toast 使用关键帧,或持续时间让下拉菜单显得迟缓。
绝不要把动效方案当作菜单列出来。直接做出决定,用一行说明理由,然后编写代码。
硬性规则
- 按顺序执行流程。 第 1 步和第 2 步决定后续一切。在确定是否需要动画之前,不要选择曲线。
- 不使用近似值。 所有曲线、持续时间和弹簧配置都必须来自下表。不要因为
cubic-bezier(0.4, 0, 0.2, 1)看起来熟悉就凭空编造。 - 扩展代码库的令牌,不要另起一套。 如果已经存在
--ease-out或持续时间刻度,就使用它。添加并行系统属于缺陷。 - **减少动态效果设置和悬停门控必须与动画一同交付,**不能留待后续。
- 使用能解决问题的最轻量工具。 不要为了淡入淡出而安装动效库。
构建流程
1. 这个元素是否应该动?
| 频率 | 决策 |
|---|---|
| 每天 100 次以上(键盘快捷键、命令面板切换) | 绝不添加动画。 到此为止。 |
| 每天数十次(悬停效果、列表导航) | 仅使用几乎察觉不到的效果——快速且轻微,或不使用动画 |
| 偶尔(模态框、抽屉、Toast) | 使用标准动画 |
| 很少/首次使用(引导、成功提示、庆祝效果) | 在这里使用惊喜预算 |
由键盘触发的操作不符合条件,不是酌情判断。 Raycast 没有打开/关闭动画是正确的,因为它每天会被打开数百次。
如果请求未通过这项检查,就直言说明,不要编写动画。改为提供非动态效果方案(立即改变状态、使用静态提示)即可。
2. 动效的目的是什么?
继续之前,请从以下词语中选一个:
- 反馈——确认界面已响应用户操作
- 空间一致性——展示元素从何处出现或消失
- 状态指示——让状态变化清晰可辨
- 避免突兀变化——衔接原本会突然跳变的内容
- 说明——演示某项功能的工作方式(仅限营销/引导)
- 惊喜——仅限很少/首次使用的场景
说不出目的吗?那就不要实现。对于频繁看到的元素,“看起来很酷”就是停止的理由。
还要检查功能性:用户正在阅读或操作的数据不应为了视觉风格而移动。装饰性的鼠标追踪效果适合营销页面,不适合银行应用中的图表。
3. 选择工具——选能满足需求的最简单方案
从上往下依次查看;找到第一个符合需求的方案后就停止。
| 需求 | 工具 |
|---|---|
| 悬停、按下、颜色变化,或通过类或属性控制的状态切换 | CSS transition |
| 挂载时的入场动画,无需 JS 状态 | CSS @starting-style |
| 预定动效,且页面忙于加载时仍须保持流畅 | CSS animation(在主线程之外运行) |
| 以 CSS 的性能实现程序化控制,无需库 | WAAPI(element.animate()) |
| 弹簧效果、布局动画、退场动画、手势驱动的值 | Motion(motion.dev) |
在负载较高时,CSS 动画优于 JS 动画——它们在主线程之外运行,而基于 requestAnimationFrame 的动画会在浏览器加载、运行脚本或绘制时丢帧。预定动效使用 CSS,动态且可中断的动效使用 JS。
如果任务需要的是组件而非动画,请使用项目现有的无障碍组件库。如果已安装 pick-ui-library,且用户需要选择库,请查阅它;未安装不应阻碍任务。添加动效时,保留键盘和焦点行为。
4. 选择属性
- 仅使用
transform和opacity。 它们不会触发布局和绘制,并且在 GPU 上运行。width/height/margin/padding/top/left会触发这三者。(clip-path是获准使用的第四种属性——参见 references/RECIPES.md。只有手风琴组件没有等效的transform实现时,才允许使用height。) - 绝不使用
scale(0)。 从scale(0.9–0.97)+opacity: 0开始。现实世界中的任何事物都不会凭空出现。 - 对于弹出层、下拉菜单、菜单和工具提示,将
transform-origin设在触发元素处;在 Base UI 中使用var(--transform-origin)。模态框不受此限制;它们不锚定在触发元素上,因此保持居中。 translate()中的百分比相对于元素自身尺寸——无论内容如何,translateY(100%)都会移动一个自身高度。优先使用百分比,而非硬编码的像素值。- 在 Motion 中,使用完整的 transform 字符串。
x/y/scale简写无法使用硬件加速,负载较高时会丢帧:
<motion.div animate={{ x: 100 }} /> // drops frames under load
<motion.div animate={{ transform: "translateX(100px)" }} /> // hardware accelerated
- 绝不要通过父元素上的 CSS 变量驱动子元素的
transform——这会为每个子元素重新计算样式。直接在元素上设置transform。
5. 缓动和持续时间——或使用弹簧效果
缓动,按优先顺序选择:
| 情况 | 缓动 |
|---|---|
| 进入或退出 | ease-out |
| 屏幕上的移动 / 变形 | ease-in-out |
| 悬停 / 颜色变化 | ease |
| 持续运动(跑马灯、进度条) | linear |
| 默认 | ease-out |
UI 中绝不要使用 ease-in。 它起步较慢,会延迟用户正在等待的确切时刻。200ms 的 ease-out 感觉比 200ms 的 ease-in 更快。
CSS 内置缓动效果太弱。请使用以下曲线:
--ease-out: cubic-bezier(0.23, 1, 0.32, 1); /* strong ease-out for UI */
--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1); /* strong ease-in-out for on-screen movement */
--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1); /* iOS-like drawer curve (Ionic) */
需要的曲线不在其中?请从 easing.dev 或 easings.co 获取。不要手动编写。
持续时间:
| 元素 | 持续时间 |
|---|---|
| 按钮按下反馈 | 100–160ms |
| 工具提示、小型弹出层 | 125–200ms |
| 下拉菜单、选择框 | 150–250ms |
| 模态框、抽屉 | 200–500ms |
| 营销 / 说明性内容 | 可以更长 |
UI 动画应控制在 300ms 以内。 180ms 的下拉菜单比 400ms 的感觉更灵敏。
在以下情况下优先使用弹簧效果:具有惯性的拖动、应该显得灵动的元素、用户可中断或反转的手势,或装饰性的鼠标跟踪:
{ type: "spring", duration: 0.5, bounce: 0.2 } // Apple-style — easier to reason about
{ type: "spring", mass: 1, stiffness: 100, damping: 10 } // traditional physics — more control
将弹跳度保持在 0.1–0.3,并且大多数 UI 中都应避免弹跳——仅用于拖动关闭和趣味交互。
6. 中断与退出
- 快速触发的效果要用过渡,而不是关键帧——提示条、切换开关,以及用户可能在一秒内触发两次的任何效果。过渡会从当前值重新调整目标;关键帧则会从零重新开始。
- 手势使用弹簧,因为被中断时,弹簧能延续速度。
- 以进入时的方式退出。 从底部滑入的提示条,也应从底部滑出。对称的路径能让滑动关闭的行为一目了然。
- 在用户做决定时采用不对称的时长。 用户有意操作的阶段要慢(按住以确认:线性 2s),系统响应要迅速(松开:ease-out,200ms)。
7. 减少动态效果与指针限制
每次都应随动画一并交付。
@media (prefers-reduced-motion: reduce) {
.element { animation: fade 0.2s ease; } /* keep opacity/color, drop transform-based motion */
}
@media (hover: hover) and (pointer: fine) {
.element:hover { transform: scale(1.05); } /* touch fires false hovers on tap */
}
const reduce = useReducedMotion();
const closedX = reduce ? 0 : '-100%';
减少动态效果意味着更少、更柔和的动画,而不是完全不动——保留有助于理解的过渡,移除移动和位置变化。
方案
如需常见场景的即用型实现——按钮按下、下拉菜单、工具提示、模态框、抽屉、提示条、手风琴、交错动画、按住以确认、标签指示器、滚动显现、拖动关闭——请参阅 references/RECIPES.md。请求涉及这些组件时,请加载该文件;从对应方案着手,不要从空白文件开始。
切勿交付
完成前请先自检。以下每一项都会被 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 |
以触发器为锚点的弹出层使用 transform-origin: center | var(--transform-origin)(模态框除外) |
| 提示条、切换开关或快速触发的元素使用关键帧 | CSS 过渡 |
动画处理 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 的交错间隔 |
输出
编写代码。然后用不超过几行说明:
- 限制判定结果——频率等级和明确的用途。如果请求中有内容被拒绝,请说明是哪项以及原因。
- 组成要素——每行列出工具、属性、曲线、时长或弹簧配置。
- 需要凭感觉检查的部分——如果结果取决于无法从代码判断的观感(交叉淡入淡出、弹簧的回弹效果、列表进入时的不透明度与高度平衡),请指出这一点,并说明如何检查:以 2–5× 时长播放,或在 DevTools 动画检查器中查看,逐帧检查,在真实设备上测试手势,并在第二天以全新的眼光重新审视。
不要把内容扩写成报告。代码才是交付物。
语气
观点鲜明,表达简洁。坦诚的答案若是“这个不该加动画”,就直说——这正是这项技能存在的意义。如果无法仅凭代码确定实际观感,也应如实说明,不要猜测数值。