关于 ui-animation
这项技能用于设计、实现、审查和调试界面动效,也可从录屏测量动效。内容侧重有明确目的且兼顾无障碍的 UI 动效,并提供模式、时序、性能和实现参考。
主要功能
- 01设计和调试 UI 动效
- 02发现动效机会并审查
- 03测量动效并拟合曲线
- 04CSS、WAAPI 和 JS 指导
- 05交互与无障碍原则
适用场景
- 01调整按钮、菜单或模态框过渡
- 02排查迟缓或跳动的界面动效
- 03根据录屏复现动效
- 04审查动效代码的可用性
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ nexu-io/ open-design --skill ui-animation安装后在 Claude Code 中输入/ui-animation确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
LICENSESOURCES.mdreferences/choreography.mdclip-path-techniques.mdcode-output.mdcomponent-patterns.mdcontextual-animations.mdcurve-fitting.mddebugging-symptoms.mddecision-framework.mdgesture-drag.mdinterface-sfx.mdmeasurement-guide.mdperformance-deep-dive.mdreview-format.mdscroll-animations.mdspring-animations.mdsvg-animation.mdtransition-recipes.mdvocabulary.mdscripts/extract_frames.pyfit_curves.pytrack_motion.py详细介绍
UI Animation
Open Design 执行上下文
将其用作当前界面或用户认可的简报中的功能辅助工具。保留宿主的任务路由、当前阶段、工具可用性和输出约定。规划描述动效;仅在宿主的创作阶段进行实现。如果宿主不允许,不要增加额外的审核、录制、渲染或审批阶段。复用项目现有的技术栈和动效令牌。配套技能为可选项:仅在已安装时使用;否则,应用此处的相关指导,不要臆造工具调用。仅在实际观察到测量结果时报告;明确标注视觉估算和无法执行的检查。
- 是: 设计、实现、审核、调试 UI 动效(弹簧、手势、拖动、缓动、CSS 过渡、关键帧、Motion);全面检查界面中确实能从动效中受益的时刻;从录制内容测量动效(提取帧、跟踪、拟合曲线),以生成代码和交接规范;为描述的动效命名(反向查找术语);以及控制稀疏的界面音效。
- 不是: 选择整体视觉方向、配色或排版(使用
ui-designDirection 模式);审核整个页面的 UI 质量(使用ui-designAudit 模式);或制定命名文本效果规范(在安装时使用外部animate-text技能)。
参考文件
| 文件 | 何时阅读 |
|---|---|
| references/decision-framework.md | 默认阅读:判断是否需要动画以及原因、选择缓动风格;也用于探索性检查(“哪些地方应该添加动画”) |
| references/spring-animations.md | 弹簧物理、Motion useSpring、配置弹簧参数、Apple 阻尼/响应值、不对称的打开/关闭风格、中断机制 |
| references/component-patterns.md | 按钮、Toast、交叉淡化、列表交错和移除、悬停、分步表单、Motion 布局变形和自动高度、3D |
| references/clip-path-techniques.md | 使用 clip-path 实现揭示、选项卡、按住删除、对比滑块 |
| references/gesture-drag.md | 拖动、滑动关闭、惯性、指针捕获、速度交接、惯性投影、旋钮拖动、定位点、轮播图 touch-action |
| references/scroll-animations.md | 滚动触发的揭示、擦洗式/滚动驱动的动画(animation-timeline、useScroll)、视差、粘性滚动叙事,以及何时不应使用滚动动画 |
| references/performance-deep-dive.md | 卡顿、CSS 与 JS、WAAPI、CSS 变量陷阱、Framer Motion 注意事项 |
| references/debugging-symptoms.md | 动画感觉不对劲,但说不出原因:按症状索引的表格,涵盖迟缓、机械、廉价、跳动和触发异常的动效 |
| references/svg-animation.md | 矢量图动画:线条绘制(stroke-dashoffset)、SVG transform-origin 陷阱、路径变形、抖动、环境动效 |
| references/review-format.md | 审查动画代码:十项标准(每项都包含一眼识别问题的触发条件)、Before/After/Why 表格、Block/Approve 判定 |
| references/contextual-animations.md | 上下文相关的图标切换、逐词交错入场、弱化外围元素、固定偏移量的退出 |
| references/transition-recipes.md | 添加 CSS 过渡:容器变形、卡片尺寸调整、徽标、下拉菜单和工具提示(带库来源变量)、模态框(使用 @starting-style 入场)、面板和抽屉、页面滑动、图标切换、数字弹入、里程表滚动、文本切换、成功、头像悬停、错误抖动 |
| references/measurement-guide.md | 逆向工程:需要测量什么、目测与脚本、读取 metrics.json、选择 ROI |
| references/curve-fitting.md | 逆向工程:读取 fit_curves.py 输出、弹簧与贝塞尔曲线、评估拟合误差、不对称的打开/关闭 |
| references/code-output.md | 逆向工程:生成 CSS、Motion/Framer Motion、SwiftUI、React Native、UIKit 代码 |
| references/choreography.md | 逆向工程:多元素/多阶段动效:交错、先模糊后移动、各边分别稳定 |
| references/vocabulary.md | 为用户描述模糊的动效命名(“……那种效果叫什么?”) |
| references/interface-sfx.md | 点击音效、界面音频、UI 音效、触觉反馈与声音的结合,或“为什么 Web 对声音如此抗拒” |
核心规则
- 为提供反馈、明确方向、保持连续性或带来刻意设计的惊喜而添加动画。如果只是“看起来很酷”,而且用户会经常看到,就不要添加。
- 键盘焦点和重复导航应即时响应。状态转换可以有动画,但焦点和任务完成不能因此延迟。
- 对可中断的 UI,优先使用 CSS transitions:被中断时,关键帧动画会从头开始,而 transitions 会调整目标。仅在序列预先确定时使用关键帧。
- 实现优先级:CSS transitions > WAAPI > CSS keyframes > JS(
requestAnimationFrame);负载较高时,CSS 仍能保持流畅,而 JS 会掉帧。 - 采用非对称时长:偶尔发生的交互可以稍慢地进入、快速退出。对于高频、短暂的 UI(悬停高亮、弹出层、面板切换),则反过来:立即进入(0ms),短暂淡出后退出(100-150ms),让操作显得即时。
- 可点击控件在
:active时立即按下(0ms),并设置touch-action: manipulation。 - 使用
@starting-style实现 DOM 元素进入;不支持时改用data-mounted属性。 - 添加少量
filter: blur(2px),可以掩盖内容切换时生硬的交叉淡化。
动效设计原则
- 保持连续,避免瞬移。 在两种状态下都可见的元素应原地过渡;元素应从其所在位置展开,而不是淡入一个新实例。切勿复制持久元素,也不要在包含相同组件的视图之间硬切;硬切会丢失空间背景。
- 动效方向与位置一致。 标签页和轮播图的过渡方向应与空间布局相符(向前时从左向右,向后时从右向左)。
- 从触发元素处展开。 覆盖层、托盘和面板应从打开它们的元素处向外展开;从屏幕中央进入会破坏空间方向感。如果形状允许,更好的做法是让触发元素变成表面(参见容器变形方案)。
- 就地确认,而非放在角落。 操作结果应显示在触发操作的控件上:按钮变为“已复制”,停留片刻后再恢复。放在远处角落的 Toast 会让用户移开视线,离开刚刚操作的对象,去确认操作是否成功。仅当结果没有屏幕上的来源(例如后台任务完成、收到消息)时,才使用角落 Toast。
- 成对状态同时添加动效。 如果打开时有动画,关闭时也应有动画。如果悬停时有动效,焦点和按下状态也应提供同等反馈。不要只打磨重复交互中的一半。
- 交互越少见,越可以增添个性。 高频操作的动效必须让人几乎察觉不到。
- 动效能提升感知速度。 即使加载时长完全相同,平滑过渡也会比硬切感觉更快。
动画内容
- 移动:仅使用
transform和opacity;它们不会触发布局和绘制。 - 状态反馈:可以使用
color、background-color和opacity。 - 切勿为布局属性(
width、height、top、left)添加动画;它们会在每一帧触发布局重新计算。(例外情况:刻意对容器添加补间动画,参见卡片尺寸调整和容器形变的方案。) - 切勿使用
transition: all;它会为非预期属性添加动画,还会悄然将未来新增的属性纳入动画。请明确列出属性。 - 避免为核心交互添加
filter动画;若无法避免,请将模糊度保持在 ≤ 20px(重度模糊开销很大,尤其是在 Safari 中)。 - SVG:在
<g>包装元素上应用变换,并设置transform-box: fill-box; transform-origin: center;否则元素会围绕画布原点旋转或缩放。线条绘制、路径变形和 Motion SVG 原点覆盖详见 references/svg-animation.md。 transform: scale()也会缩放子元素(图标、文本和边框都会按比例缩放),这与width/height不同:它适合用于按压反馈,但若内部元素必须保持固定尺寸,请考虑这一点。- 切换主题时禁用过渡(
[data-theme-switching] * { transition: none !important }),否则所有主题属性都会同时播放动画。在切换完成后强制回流(void document.body.offsetHeight),并在下一帧移除覆盖规则;或者使用next-themes的disableTransitionOnChange。
缓动默认值
| 元素 | 时长 | 缓动 |
|---|---|---|
| 按钮按压反馈 | 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 |
| 悬停(变换/缩放) | 100-150ms | 进入曲线 |
| 插画/营销内容 | 最长 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)(起始斜率极陡;因此 500ms 不会显得很慢) - 指数减速:
cubic-bezier(0.19, 1, 0.22, 1),用于戏剧性的显现效果、卡片悬停和文本显现 - 按压:
cubic-bezier(0.25, 0.46, 0.45, 0.94),用于按钮按压反馈 - 屏幕内移动:
cubic-bezier(0.645, 0.045, 0.355, 1),用于在屏幕内往返的移动
避免在 UI 中使用 ease-in:它的起步很慢,元素会滞后于用户操作,让人感觉迟钝。相比内置的 ease/ease-out,更推荐使用 easing.dev 的自定义曲线;内置曲线的加速度较柔和,显得轻柔而不够果断。
过渡决策规则
先匹配 UI 元素,再从 references/transition-recipes.md 中选择方案:
| UI 模式 | 方案 |
|---|---|
| 触发器 + 浮动圆点/计数 | 通知徽标 |
| 触发器扩展为其打开的界面 | 容器变形 |
| 触发器 + 锚定界面 | 菜单下拉 |
| 页面上方居中的界面 | 模态对话框 |
| 滑入现有容器的面板 | 面板显现 |
| 列表 ↔ 详情或向导步骤 | 页面并排滑动 |
| 元素尺寸变化 | 卡片缩放 |
| 文本原位更新 | 文本状态切换 |
| 同一位置的两个图标 | 图标切换 |
| 数字自行出现 | 数字弹出 |
| 用户正在控制的数字 | 里程表数字滚动 |
| 确认 / 成功时刻 | 成功庆祝 |
| 水平堆叠中悬停的项目 | 头像组悬停 |
| 表单验证错误 | 错误状态抖动 |
除非设计需要 JS 编排,否则优先选择开销更低的过渡(仅 CSS)。
空间与时序
- 弹出层的
transform-origin设在触发器处(模态框保持center);对话框/菜单的入场缩放使用scale(0.9-0.96),而非scale(0)(小型弹出层取较低值,完整对话框取较高值:较大的界面本身就会在绝对像素上移动很远);错开 30-50ms(总时长低于 300ms,最重要的元素先动)。完整规则和代码见 references/transition-recipes.md 和 references/contextual-animations.md。 - 配对元素规则: 一起动效的元素(模态框 + 遮罩层、工具提示 + 箭头、FAB + 标签)必须使用相同的缓动和时长。时序不匹配通常会让人觉得“哪里不对劲”。
无障碍
- 将悬停(动效和绘制)限制在
@media (hover: hover) and (pointer: fine)条件下,或让触屏设备在点按时重现悬停效果。添加条件前先检查生成的 CSS;Tailwind v4 已经会将hover:包在@media (hover: hover)中。 - 直接操控期间,让元素紧跟指针,不使用缓动;释放后再添加缓动。
- 可选的界面音效:少量使用、由手势解锁,且仅用于增强确认反馈。参见 references/interface-sfx.md。
性能
- 使用
IntersectionObserver暂停屏幕外的循环动画;即使不可见,它们也会消耗 GPU。 - 仅在重度动效期间对
transform/opacity切换will-change;结束后移除。每次提升图层都会消耗合成器内存;在许多元素上永久提升图层比完全不提升更糟。 - 不要通过容器上的 CSS 变量实现拖动动画;每次更新都会为所有子元素重新计算样式。直接在移动元素上设置
transform。 - 轴向移动和拖动默认使用 Motion 的
x/y值(可绕过 React 重新渲染)。当一个所有者必须组合多个变换函数、与非 Motion 代码互操作,或在主线程繁忙时保持流畅,可使用完整的transform字符串:这些简写会在requestAnimationFrame上运行,当动效与导航、数据加载或 hydration 同时发生时会丢帧;此时 CSS/WAAPI 仍能保持流畅。 - 只在某些时候卡顿的动效(打开时、导航期间、数据到达时)通常是同一帧中有长任务,而非动画开销过大。不要在同一帧中启动动画并执行耗时工作:先启动动效,等一帧呈现后再执行工作,或将其延迟到
transitionend。 - 有关 WAAPI、合成图层、动画期间的长任务,以及 CSS 与 JS 对比表,请参见 references/performance-deep-dive.md。
反模式
上文未涵盖的高风险失败情形:
- 未经用户触发便在挂载时播放动画:意外的动效会令人迷失方向,因为用户并未做任何事来触发它。
- 拖动边界处生硬地停止会让人觉得出了故障;应加入摩擦或阻尼,让越过边界后的移动逐渐减弱(参见 gesture-drag 参考文档)。
- 同时为容器和其子元素设置错峰动画:每个容器只选择一种入场动画。如果面板滑入,其中的内容应在面板到达时就已可见。
- 首个工具提示打开后仍播放动画:同组后续工具提示应即时打开,否则工具栏会显得迟钝。
- 滚动显示产品 UI、首屏内容或页面的每个区块:滚动显示只适用于营销页面中少数精心挑选的时刻,只播放一次,向上滚动时绝不重新播放(参见 references/scroll-animations.md)。
- 为经滚动驱动的动效设置缓动或时长:此时滚动位置就是时钟,因此任何曲线或时长都会让动效落后于滚动条。此处应使用
linear且不设时长,只有此处如此。 - 仅通过 CSS 处理减少动态效果:Motion 的 JS 驱动动画会忽略
@media (prefers-reduced-motion)代码块,因此 CSS 条件虽能通过审核,空间位移仍会播放。请用<MotionConfig reducedMotion="user">包裹应用,或根据useReducedMotion()进行分支处理。反向过度修正也不妥:在 reduce 条件下全局应用* { transition: none !important },还会去除减少动态效果模式应保留的不透明度和颜色反馈。 - 为新项目安装
framer-motion:该软件包现已更名为motion,React 导入路径为motion/react。旧软件包仍可解析,因此混用的代码库虽然能编译,却会同时打包两份库。
工作流
复制并跟踪:
Animation progress:
- [ ] Step 1: Decide whether the interaction should animate
- [ ] Step 2: Choose purpose, easing, and duration
- [ ] Step 3: Pick the implementation style
- [ ] Step 4: Load the relevant component or technique reference
- [ ] Step 5: Validate timing, interruption, and device behavior
- 回答 references/decision-framework.md 中的四个问题:是否添加动画?目的是什么?使用什么缓动?速度多快?
- 根据上方的缓动默认值表选择时长。如果对数值有争议,可在 DevTools 的贝塞尔曲线编辑器(Chrome 或 Firefox;Safari 没有该功能)中,针对实际组件实时调整,然后将具体数值粘贴到源码中其他时间常量旁边:DevTools 中的修改会在页面导航时消失。
- 选择实现方式:CSS transition > WAAPI > spring > keyframe > JS。
- 加载适用于该组件或技术的参考文档。
- 审核时,遵循 references/review-format.md 中的严格要求:根据十项标准进行衡量,输出 Before/After/Why 表格,然后给出分级结论,并以 Block/Approve 决策收尾。
验证
为每项检查提供证据(DevTools 中的观察结果,而不是“看起来没问题”):
- 在差异中搜索布局属性的过渡(
width、height、top、left)以及transition: all。 - 快速反复切换组件;确认过渡会重新定位,而不是从零重新开始。
- 在 DevTools 的 Animations 面板中将速度降至 10%,以发现全速播放时难以察觉的时序和
transform-origin问题。 - 确认
will-change只在动画期间启用,而非永久设置;循环动画在移出屏幕后会暂停。 - 在真实设备上测试触摸交互;模拟器会低估手势和点击触发悬停的问题。
- 遵循
prefers-reduced-motion:将空间位移替换为即时状态变化或克制的淡入淡出。使用animation-play-state: paused暂停循环装饰(不要用display: none将其移除)。保留明确由用户触发的反馈。在该模式下完成同一项任务,进行验证。
探索工作流
对于“这里应该添加什么动画”,请运行 references/decision-framework.md 中的 Discovery 扫描。报告有明确目的且使用频率足够高的机会,以及被否决的方案。只有当实现工作在范围内时,才实施建议。
逆向工程工作流
使用此分支从屏幕录制中测量现有动画,然后生成可复现该动画的代码和交接规范。scripts/ 下的脚本是标准且确定性的路径;请运行这些脚本,而不要自行重构其逻辑。
下面的每条 scripts/ 命令都应相对于已安装的技能目录解析,而不是应用的工作目录。
**依赖项:**运行测量脚本前,请检查是否有 ffmpeg / ffprobe,以及安装了
opencv-python、numpy 和 scipy 的 Python 环境。
不要假设 Open Design 已安装这些依赖项。请复用可用的隔离式
Python 环境;任何依赖项配置都必须遵守主机的授权要求。
如果只有 ffmpeg 可用,请提取帧,并将分析标注为视觉估算。如果无法检查媒体,请说明
缺少的能力;绝不要声称测得了时间、拟合了曲线或比较了录制结果。
每次提取都使用全新的任务专用输出目录,避免旧帧污染联系表。以下脚本是可选的测量工具,
并非实现常规界面动画的必要条件。
Reverse-engineer progress:
- [ ] Step 1: Extract frames + contact sheet (per direction if open differs from close)
- [ ] Step 2: Vision pass: identify element, effects, phases
- [ ] Step 3: Decide precision (eye-only vs scripted)
- [ ] Step 4: Track motion and fit curves (if escalating)
- [ ] Step 5: Annotate choreography (delays, asymmetry)
- [ ] Step 6: Emit code for the target(s)
- [ ] Step 7: Validate against the recording
- **提取。**运行
python3 scripts/extract_frames.py <video> <outdir>。用--start/--duration裁剪出仅包含过渡的片段;如果交互包含打开和关闭两个过程,就裁剪两个时间窗口,并针对每个方向各运行一次流水线(它们几乎从来不是镜像效果)。让--fps与源视频匹配(用ffprobe检查),绝不要以高于源视频的帧率采样。先打开contact_sheet.png。 - **视觉检查。**指出发生移动的元素、所有效果(平移、缩放(通常各向异性)、不透明度、模糊、圆角半径、阴影、颜色)和各个阶段,并注明哪些属性先变化、哪些后变化。使用
references/measurement-guide.md中的检查清单。 - **确定精度。**简单淡入淡出或线性滑动:从联系表读取时间,跳过第 4 步,直接进行第 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.1x,确认阶段顺序和过度拉伸效果都得以保留。确认代码只对transform、opacity和filter添加动画。
逆向分析注意事项:
fit_curves.py默认使用--fps 30:如果以 60 帧提取却使用默认值拟合,所有duration_ms都会翻倍,拟合出的刚度则会降至四分之一。拟合时务必传入提取使用的帧率。- 以高于源视频的帧率采样会产生重复帧:以 60 帧提取 24 fps 的 GIF,会因
metrics.json中出现平台状连续帧而增大拟合误差。请检查并匹配源帧率。 - 录屏会丢帧,iOS/QuickTime 录制的帧率也可能可变;连续相同的行表示重复帧,而不是暂停。如果平台状连续帧占多数,请以更稳定的帧率重新录制。
- 将打开和关闭过程作为独立片段测量,并分别报告两条曲线;绝不要拟合一条曲线后反向复用(参见
references/choreography.md)。拟合error高于 0.08 时应视为可疑。
相关技能
product-design:明确有哪些状态、操作会影响什么,以及操作是否可逆。当手势替代控件时,应先转到这里,因为滑动删除和长按确认会改变用户能执行的操作,然后才会改变操作方式。ui-designDirection 模式:视觉方向、配色方案、字体;先确定视觉系统,再调整动效。ui-designAudit 模式:页面/功能级 UI 质量审查。动效设计及修复属于此模式。- 可选的外部
animate-text技能(如已安装):精选的命名文本效果(打字机、逐行显示、交错构建),附带精确的 JSON 规格。
上游评估样例不属于此运行时包。源文件和适配详情记录在 SOURCES.md 中。