Designs, implements, reviews, debugs, and reverse-engineers UI motion: CSS transitions, keyframes, springs, gestures, drag, easing, timing, framer-motion, and animation curves from screen recordings. Use when asked to "add animations", "make this feel smooth", "review my animations", "add a swipe gesture", "match this easing", "reverse engineer this animation", "extract the animation curve", or "what's it called when..." to name a motion effect from a vague description. For visual direction use ui-design; for page-level UI audit use ui-audit.
UI 动画
- 是: 设计、实现、审查、调试 UI 动效(弹簧、手势、拖拽、缓动、CSS 过渡、关键帧、framer-motion),从录制中测量动效(提取帧、跟踪、拟合曲线)以生成代码和交接规范,以及命名描述的动效(反向查找词汇)。
- 不是: 选择整体视觉方向、调色板或排版(使用
ui-design),审计整个页面的 UI 质量(使用ui-audit),或命名文本效果规范(如果安装了外部animate-text技能,则使用它)。
从录制中逆向工程动效的规范路径:将“逆向工程这个动画”和“匹配这个缓动”路由到这里,而不是单独的技能。如果输入是屏幕录制或视频,您正在测量动效:遵循逆向工程工作流。否则(设计、实现、审查)使用下面的规则和工作流。
参考文件
| 文件 | 何时阅读 |
|---|---|
| references/decision-framework.md | 默认:决定是否/为什么动画,选择缓动特性 |
| references/spring-animations.md | 弹簧物理、framer-motion useSpring、配置弹簧参数、Apple 阻尼/响应值、中断机制 |
| references/component-patterns.md | 按钮、弹出框、工具提示、抽屉、模态框、带动画的 toast |
| references/clip-path-techniques.md | clip-path 用于揭示、标签页、长按删除、对比滑块 |
| references/gesture-drag.md | 拖拽、滑动关闭、动量、指针捕获、速度交接、动量投影 |
| references/performance-deep-dive.md | 卡顿、CSS vs JS、WAAPI、CSS 变量陷阱、Framer Motion 注意事项 |
| references/review-format.md | 审查动画代码:十个标准(每个都有见即触发标志)、之前/之后/原因表格、阻止/批准裁决 |
| references/contextual-animations.md | 上下文图标切换、单词级交错进入、固定偏移退出 |
| references/transition-recipes.md | 安装 CSS 过渡:卡片调整大小、徽章、下拉菜单、模态框、面板、页面滑动、图标切换、数字弹出、文本切换、成功、头像悬停、错误抖动 |
| references/measurement-guide.md | 逆向工程:测量什么、肉眼 vs 脚本、读取 metrics.json、选择 ROI |
| references/curve-fitting.md | 逆向工程:读取 fit_curves.py 输出、弹簧 vs 贝塞尔、判断拟合误差、非对称打开/关闭 |
| references/code-output.md | 逆向工程:为 CSS、Motion/Framer Motion、SwiftUI、React Native、UIKit 生成代码 |
| references/choreography.md | 逆向工程:多元素/多阶段动效:交错、模糊再移动、每边稳定 |
| references/vocabulary.md | 命名用户模糊描述的动效(“这个叫什么来着……”) |
核心规则
- 为反馈、方向、连续性或精心设计的愉悦感而动画。如果只是“看起来很酷”且用户经常看到,就不要做。
- 永远不要为键盘触发的操作(快捷键、箭头导航、Tab/焦点)添加动画;它们会不断重复,动画会让它们感觉缓慢。
- 对于可中断的 UI,优先使用 CSS 过渡:关键帧在中断时从零重新开始,过渡会重新定位。仅对预定序列使用关键帧。
- 实现优先级:CSS 过渡 > WAAPI > CSS 关键帧 > JS(
requestAnimationFrame);在负载下 CSS 保持流畅,而 JS 会掉帧。 - 非对称时序:偶尔的交互可以稍慢进入,快速退出。高频短暂 UI(悬停高亮、弹出框、面板切换)则相反:立即进入(0ms),短暂淡出(100-150ms),使操作感觉即时。
- 使用
@starting-style处理 DOM 进入;在不支持的地方回退到data-mounted属性。 - 小的
filter: blur(2px)可以隐藏交换内容之间的粗糙交叉淡入淡出。
动效设计原则
- 连续性优于瞬移。 在两个状态中都可见的元素在原地过渡;从元素所在位置展开,而不是淡入新实例。永远不要复制持久元素或在共享组件的视图之间硬切;硬切会丢失空间上下文。
- 方向性动效匹配位置。 标签页和轮播过渡的动画方向与空间布局匹配(前进从左到右,后退从右到左)。
- 从触发器出现。 覆盖层、托盘和面板从打开它们的元素向外动画;通用的屏幕中心进入会破坏空间方向感。
- 成对状态一起动画。 如果打开有动画,关闭也有动画。如果悬停有动效,焦点和按下状态获得等效反馈。不要只打磨重复交互的一半。
- 愉悦感与频率成反比。 较少的交互获得更多个性;高频操作必须不可见。
- 动效增强感知速度。 流畅的过渡感觉比硬切更快,即使在相同的加载时间下。
动画什么
- 移动:仅
transform和opacity;它们跳过布局和绘制。 - 状态反馈:
color、background-color和opacity是可接受的。 - 永远不要动画布局属性(
width、height、top、left);它们每帧触发布局重新计算。(例外:有意的容器大小调整补间,参见卡片调整大小配方。) - 永远不要使用
transition: all;它会动画非预期的属性并静默地采用未来的属性。显式列出它们。 - 避免为核心交互使用
filter动画;如果不可避免,保持模糊 ≤ 20px(重度模糊代价高昂,尤其是在 Safari 中)。 - SVG:在
<g>包装器上应用变换,并设置transform-box: fill-box; transform-origin: center;否则它们会围绕画布原点旋转/缩放。 transform: scale()也会缩放子元素(图标、文本、边框按比例缩放),与width/height不同:这是按下反馈的一个特性,但当内部元素必须保持固定大小时要考虑。- 在主题切换期间禁用过渡(
[data-theme-switching] * { transition: none !important }),否则每个主题属性都会同时动画。
缓动默认值
| 元素 | 持续时间 | 缓动 |
|---|---|---|
| 按钮按下反馈 | 100-160ms | cubic-bezier(0.22, 1, 0.36, 1) |
| 工具提示、小弹出框 | 125-200ms | ease-out 或进入曲线 |
| 下拉菜单、选择框 | 150-250ms | cubic-bezier(0.22, 1, 0.36, 1) |
| 模态框、抽屉 | 200-350ms | cubic-bezier(0.22, 1, 0.36, 1) |
| 屏幕上移动/滑动 | 200-300ms | cubic-bezier(0.25, 1, 0.5, 1) |
| 页面过渡 | 250-400ms | 进入或移动曲线 |
| 简单悬停(颜色/不透明度) | 200ms | ease |
| 说明性/营销 | 最多 1000ms | 弹簧或自定义 |
保持常规 UI 在 300ms 以下;根据距离缩放持续时间(全屏滑动可以超过 300ms,6px 工具提示移动保持在 150ms 以下)。
命名曲线
- 进入:
cubic-bezier(0.22, 1, 0.36, 1)用于进入和基于变换的悬停 - 移动:
cubic-bezier(0.25, 1, 0.5, 1)用于滑动、抽屉、面板 - 抽屉(类似 iOS):
cubic-bezier(0.32, 0.72, 0, 1)
避免在 UI 中使用 ease-in:它开始缓慢,因此元素滞后于用户操作,感觉迟钝。优先使用来自 easing.dev 的自定义曲线,而不是内置的 ease/ease-out,后者的温和加速读起来柔和,不果断。
过渡决策规则
首先匹配 UI 元素,然后从 references/transition-recipes.md 中选择配方:
| UI 模式 | 配方 |
|---|---|
| 触发器 + 浮动点/计数 | 通知徽章 |
| 触发器 + 锚定表面 | 菜单下拉 |
| 页面顶部的居中表面 | 模态对话框 |
| 滑入现有容器的面板 | 面板揭示 |
| 列表 ↔ 详情或向导步骤 | 页面并排滑动 |
| 元素尺寸变化 | 卡片调整大小 |
| 文本原地更新 | 文本状态切换 |
| 同一位置的两个图标 | 图标切换 |
| 数字更新 | 数字弹出 |
| 确认/成功时刻 | 成功庆祝 |
| 水平堆栈中的悬停项 | 头像组悬停 |
| 表单验证错误 | 错误状态抖动 |
优先选择开销较低的过渡(仅 CSS),除非设计需要 JS 编排。
空间和序列
- 弹出框的
transform-origin在触发器处(模态框保持center),对话框/菜单进入从scale(0.85-0.9)而不是scale(0),以及 30-50ms 的交错(总计低于 300ms,最重要的元素领先)。完整规则和代码见 references/component-patterns.md 和 references/contextual-animations.md。 - 成对元素规则: 一起动画的元素(模态框 + 覆盖层、工具提示 + 箭头、FAB + 标签)必须共享缓动和持续时间。不匹配的时序通常是“感觉不对劲”的原因。
无障碍
- 每个动画都需要一个
prefers-reduced-motion: reduce路径:禁用变换/关键帧动效,保持即时状态更改或仅不透明度淡入淡出。所有配方都包含保护。 - 将悬停动画限制在
@media (hover: hover) and (pointer: fine)后面,否则触摸设备会在点击时重放悬停。Tailwind v4 的hover:工具会自动应用此规则;在那里跳过手动查询。 - 在直接操作期间,保持元素锁定在指针上,没有缓动;仅在释放后添加缓动。
性能
- 使用
IntersectionObserver暂停屏幕外的循环动画;即使不可见,它们也会消耗 GPU。 - 仅在重度动效期间切换
will-change,且仅用于transform/opacity;之后移除。每次提升都会消耗合成器内存;跨多个元素的永久提升比没有更糟。 - 不要通过容器上的 CSS 变量来动画拖拽;每次更新都会重新计算所有子元素的样式。直接在移动元素上设置
transform。 - Motion 的
x/y值是轴移动和拖拽的默认值(它们绕过 React 重新渲染)。仅当一个所有者必须组合多个变换函数或与非 Motion 代码互操作时,才使用完整的transform字符串。 - 有关 WAAPI、合成层以及 CSS vs JS 比较表,请参见 references/performance-deep-dive.md。
反模式
上面未涵盖的高信号失败:
- 在没有用户触发的情况下在挂载时动画:意外的动效会迷失方向;用户没有做任何导致它的事情。
- 拖拽边界上的硬停止感觉破碎;应用摩擦/阻尼,使移动在超出边界后逐渐减弱(参见手势拖拽参考)。
- 同时动画容器及其子元素的交错:每个容器选择一个进入方式。如果面板滑入,其内容应该在到达时已经可见。
- 第一个工具提示打开后,后续工具提示的动画:组中的后续工具提示立即打开,否则工具栏会感觉迟钝。
(变换所有者冲突和快速连续关键帧失败已在上面性能和核心规则中规范说明。)
工作流
复制并跟踪:
动画进度:
- [ ] 步骤 1:决定交互是否应该动画
- [ ] 步骤 2:选择目的、缓动和持续时间
- [ ] 步骤 3:选择实现风格
- [ ] 步骤 4:加载相关组件或技术参考
- [ ] 步骤 5:验证时序、中断和设备行为
- 回答 references/decision-framework.md 中的四个问题:动画?目的?缓动?速度?
- 从上面的缓动默认值表中选择持续时间。
- 选择实现:CSS 过渡 > WAAPI > 弹簧 > 关键帧 > JS。
- 加载组件或技术的参考。
- 审查时,应用 references/review-format.md 中的严格姿态:对照十个标准进行测量,输出之前/之后/原因表格,然后分层裁决,最终以阻止/批准决定结束。
验证
为每个检查提供证据(DevTools 观察,而不是“看起来不错”):
- 在 diff 中搜索布局属性过渡(
width、height、top、left)和transition: all。 - 快速切换组件;确认过渡重新定位而不是从零重新开始。
- 在 DevTools 动画面板中减速到 10%,以捕捉在全速下不可见的时序和
transform-origin问题。 - 模拟
prefers-reduced-motion: reduce(DevTools 渲染面板),并确认每个动画都有减少路径。 - 确认
will-change在动画周围切换,而不是永久设置,并且循环动画在屏幕外暂停。 - 在真实设备上测试触摸交互;模拟器会低估手势和点击悬停问题。
- 第二天用新的眼光再次审查;开发过程中遗漏的不完美之处会凸显出来。
逆向工程工作流
使用此分支从屏幕录制中测量现有动画,然后生成代码和交接规范以重现它。scripts/ 下的脚本是规范的确定性路径;运行它们而不是重建它们的逻辑。
依赖项: ffmpeg 用于帧提取(brew install ffmpeg);Python 需要 pip install opencv-python numpy scipy 用于跟踪和曲线拟合。优雅降级:仅使用 ffmpeg 可以提取帧并进行视觉推理;跟踪和拟合需要 Python 包。
逆向工程进度:
- [ ] 步骤 1:提取帧 + 联系表(如果打开与关闭不同,则按方向)
- [ ] 步骤 2:视觉检查:识别元素、效果、阶段
- [ ] 步骤 3:决定精度(仅肉眼 vs 脚本)
- [ ] 步骤 4:跟踪动效并拟合曲线(如果升级)
- [ ] 步骤 5:注释编排(延迟、不对称)
- [ ] 步骤 6:为目标生成代码
- [ ] 步骤 7:对照录制验证
- 提取。 运行
python3 scripts/extract_frames.py <video> <outdir>。使用--start/--duration裁剪到仅过渡;如果交互同时有打开和关闭,裁剪两个窗口并为每个方向运行一次管道(它们几乎从不是镜像)。将--fps与源匹配(使用ffprobe探测),永远不要采样高于源速率。首先打开contact_sheet.png。 - 视觉检查。 命名移动的元素、每个效果(平移、缩放(通常各向异性)、不透明度、模糊、圆角、阴影、颜色)以及阶段,注意哪个属性领先和滞后。使用
references/measurement-guide.md中的检查清单。 - 决定精度。 简单的淡入淡出或线性滑动:从联系表读取时序,跳到步骤 5。弹性、弹簧或多属性动效:升级到步骤 4(目测弹簧不可靠)。
- 跟踪和拟合。 运行
python3 scripts/track_motion.py <outdir>获取metrics.json(传递--bbox X,Y,W,H以隔离一个元素),然后运行python3 scripts/fit_curves.py <outdir>/metrics.json获取弹簧参数、三次贝塞尔曲线和每个属性的拟合误差。传递与提取时相同的--fps。阅读references/curve-fitting.md以选择模型;两者误差高意味着多阶段动效(分割并拟合每个段)。 - 注释。 加载
references/choreography.md。构建时序偏移表(每个属性何时开始和稳定);领先/滞后间隙和过度拉伸比任何单个曲线都更能传达感觉。 - 生成。 将拟合参数替换到
references/code-output.md中的模板中,针对目标。保持移动在transform/opacity上。当打开和关闭不同时,生成两个过渡,加上整合的交接规范,以便无需视频即可实现。 - 验证。 重新推导:播放生成的动画,屏幕录制它,再次通过
extract_frames.py运行,并排比较联系表。减速到 0.1 倍以确认阶段顺序和过度拉伸仍然存在。确认代码仅动画transform、opacity和filter。
逆向工程陷阱:
fit_curves.py默认--fps 30:以 60 提取但以默认值拟合,每个duration_ms加倍,而拟合的刚度降至四分之一。始终将提取的 fps 传递给拟合。- 采样高于源速率会复制帧:24 fps 的 GIF 以 60 提取会因
metrics.json中的平台期运行而夸大拟合误差。探测并匹配源速率。 - 屏幕录制会掉帧,iOS/QuickTime 捕获是可变帧率;连续相同的行是重复帧,而不是暂停。如果平台期占主导,以更稳定的速率重新录制。
- 将打开和关闭作为单独的片段测量并报告两条曲线;永远不要拟合一条曲线然后反向使用(参见
references/choreography.md)。将拟合error高于 0.08 视为可疑。
相关技能
ui-design:视觉方向、调色板、排版;在调整动效之前确定视觉系统。ui-audit:页面/功能级 UI 质量审计;其动效发现会路由回这里进行修复。- 可选的外部
animate-text技能(如果安装):精选的命名文本效果(打字机、行揭示、交错构建)带有精确的 JSON 规范。






