UX 动效
在设计并编写交互时应用这些规则。首先检查项目中的动效令牌:仓库中的 AGENTS.md、CLAUDE.md、令牌文件以及设计文档会覆盖下面的默认值。
始终在线的核心规则
- 默认决策:功能 UI 通常位于 120–180ms 区间。项目令牌优先。
- 为空间连续性、状态变化、输入反馈或避免突兀的闪烁而添加动效。
- 优先使用命名的
transform和opacity过渡。切勿使用transition: all。 - 进入的界面应减速;退出的界面应加速。退出动画可以更短。
- 缩放的起始界面靠近
scale(0.96),绝不能从scale(0)开始。 - 在
prefers-reduced-motion: reduce下,动效应可被替换;保留理解能力和键盘访问。 - 为拖拽操作捕获指针,防止多点触控干扰,并在真实硬件上测试手势。
- 在编写动效或操作通用浮层之前,请完整阅读此文件。仅当涉及剪裁路径、3D、WAAPI 或性能深入探讨时,才使用
REFERENCE.md。
1. 判断是否值得添加动效
将曝光频率作为约束条件:不断重复的操作应瞬时或接近瞬时完成;偶尔出现的界面可以建立空间连续性;罕见的解释性时刻可以承载更多个性。动效必须用于阐明变化、确认输入、保留位置或防止突兀的出现。如果它不满足这些条件,请移除它。不要让键盘快捷键等待装饰性动效。
2. 选择时长
120–180ms 区间是一个默认决策:它有意比 Material 3 发布的 short3 (150ms)、short4 (200ms) 及标准过渡范围 (250–400ms) 更紧凑。这表达了对缓慢功能 UI 的偏好,而非发布的无障碍或平台要求。
| 界面 | 起始点 |
|---|---|
| 按下反馈 | 100–140ms |
| 工具提示或小弹出框 | 120–160ms |
| 菜单或下拉菜单 | 140–180ms |
| 模态框 | 180ms |
| 面板 | 180ms;若穿越视口大部分区域,最长可达 250ms |
退出动画使用较短的一端。这些是默认值,而非覆盖产品令牌。
3. 选择缓动
进入的界面应带有强调效果并平稳停止(减速);退出应迅速清除(加速)。Material 3 明确命名了这些方向:
:root {
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
--ease-enter: cubic-bezier(0.05, 0.7, 0.1, 1); /* emphasized-decelerate */
--ease-exit: cubic-bezier(0.3, 0, 0.8, 0.15); /* emphasized-accelerate */
}
仅对类时间进度使用 linear。对经过刻意中间点的状态使用 ease-in-out 曲线。避免仅凭名称选择曲线:检查绘制的形状和运动方向。
4. 保持渲染低成本
优先使用 transform 和 opacity,它们可以停留在合成器上。命名属性并将布局排除在动画之外。避免对 top、left、width、height、margin 或 padding 进行动画,除非布局变化本身是目的且其成本已接受。对于固有高度,可以使用包装器将 grid-template-rows: 0fr 过渡到 1fr,内部包装器设置 min-height: 0 并由 overflow: hidden 修剪。这仍需执行布局;请进行性能分析,而非将其视为仅合成器的快捷方式。will-change 应晚用、短用,且仅在性能分析证明其有益后使用;图层会消耗资源。
5. 确保状态正确
- 为可按压控件提供可见反馈;使用现有的颜色、边框或阴影状态,或在其合适时使用小幅度的
scale(0.98)。不要覆盖定位变换。 - 保留可见的焦点、键盘激活和禁用行为。动画不能延迟焦点放置、Escape 处理或语义状态更新。
- 弹出框、菜单和工具提示通过
transform-origin从锚点展开;居中的模态框从中心展开。在状态变化期间保持原点稳定。 - 偏好对可重触发状态使用过渡。对自包含事件使用关键帧。
- 使用
@media (hover: hover) and (pointer: fine)限制仅悬停的交互。 - 对于进入元素,在支持
@starting-style的地方使用它,或者使用一个挂载状态,使初始样式在过渡开始前可被观察到。
6. 减少动效意味着有替代方案
prefers-reduced-motion 设置涵盖动效被移除、减少或替换;大规模平移和缩放可能引发前庭刺激。在能够解释状态的情况下,用透明度与颜色变化替代移动与缩放。保持焦点、加载和可见性变化可理解。不要仅仅因为用户要求减少动效就让元素消失。在 JavaScript 驱动的动画以及 CSS 中都应尊重此偏好,包括页面打开时偏好改变的情况。不要依赖 transitionend 事件来完成关键状态变更:过渡可能被禁用或取消。将覆盖范围限定到受影响的组件,而非全局强制每个动画都使用极短时长。
7. 手势
当拖拽开始时调用 setPointerCapture():后续指针事件将继续定向到捕获元素,直到释放。根据调整后的速度或距离规则进行关闭;没有通用的轻拂常量,因此要在实际设备上校准。通过阻尼而非硬边界来限制运动。一旦手势拥有指针,忽略额外的触控点。让用户能反转正在执行的手势。通过处理 pointercancel 和 lostpointercapture 来清除拖拽状态。为手势轴选择合适的 touch-action,避免不必要地阻止页面滚动;提供按钮或键盘等效操作。
8. 处理中断与弹簧
快速打开/关闭/重新打开应稳定在最新的请求状态。从当前视觉状态重新定位目标,而非从头开始重播。在替换或卸载时取消过期的回调、监听器和动画;旧的退出动画不能移除新打开的界面。根据组件的焦点生命周期,将隐藏或退出的内容移出键盘导航。
当直接操作或连续性需要时,可使用弹簧动画,但要尊重项目的库。弹簧参数在不同引擎间存在差异:不要盲目复制 stiffness 和 damping 值。在实际组件上调整稳定时间和超调量;避免文字密集界面出现弹跳,并在减动效下禁用移动。
9. 完成前验证
以 4 倍慢速回放,检查属性是否同时完成并从正确位置开始。快速切换以暴露卡顿。在开发者工具中模拟减动效。模拟拖拽超出边缘、取消、添加第二个触控点,并在物理设备上测试。对于运行时计算的动画值,请查阅 REFERENCE.md。说明实际执行了哪些检查,以及哪些需要在浏览器或物理设备上执行;单靠代码审查无法确定感知平滑度。
审查输出
在审计代码时,提供一个紧凑的表格,最严重的问题排在首位:
| 文件:行号 | 现状 | 修改为 | 原因 |
|---|---|---|---|
Menu.tsx:18 | transition: all 300ms | 命名属性和项目令牌 | 避免意外的布局动画 |
仅报告代码中可见的问题。如果差异清晰干净,则如实说明,而非编造问题。
由 mad7droid 精心整理。