关于 web-animation-design
web-animation-design 是专注于网页动效与交互设计实现的技能。它帮助团队根据现有组件选择动画方式,同时兼顾无障碍和交互行为,并在评审动效问题时明确证据范围。
主要功能
- 01选择过渡、关键帧或弹簧动画
- 02参考缓动与时长建议
- 03处理布局和共享布局动效
- 04考虑减少动态效果偏好
- 05保留焦点和键盘操作行为
- 06评审动效表现并说明证据
适用场景
- 01为菜单和模态框设计反馈动效
- 02为布局或标签状态变化添加动画
- 03评审存在动效问题的交互
- 04检查减少动态效果时的动画行为
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ aa-on-ai/ agentic-design-system --skill web-animation-design安装后在 Claude Code 中输入/web-animation-design确认已加载。
附带文件PRACTICAL-TIPS.mdreferences/transitions-candidate.md
详细介绍
Web 动画设计
范围与职责
当任务涉及动画、动态效果或交互手感,或 ADS 指出特定动态效果缺陷时,使用本文。对于普通 UI 工作,基础动态效果参考 已足够。ADS 负责任务、权限、审核和修复预算;本文是其实现专用指南,而不是第二套检查框架。
从实际请求和现有基础组件、状态及项目令牌着手。明确状态、层级、因果关系或反馈目标。如果动态效果没有价值,就保留清晰的静态反馈。审核请求应保持只读;实现请求则在现有授权范围内推进。不要用通用的就绪状态回复替代可执行的请求。
以下示例是参考 Emil Kowalski 的《Animations on the Web》课程提供的实现方案,并非必需的审美标准或依赖项。仅当项目令牌尚未定义相关选择时,才将数值示例作为起点。
审核结果
在任务现有的审核内容中,报告观察到的行为、相关状态或元素、建议的更改以及证据的局限。若有多项发现,可用前后对照表帮助比较;单项缺陷无需拘泥格式。区分确定性的状态/时序检查、实际浏览器交互、性能测量,以及用户对清晰度和手感的判断。
选择现有实现路径
- 保留现有组件和动态效果库。不要仅为采用示例而安装或迁移依赖;请核实已安装的包/版本及其支持的 API。
- 对交互式悬停、按下和打开/关闭状态变化,若 CSS 过渡足以满足需求,优先使用它。它可以从当前插值状态重新调整目标。
- 关键帧描述时间轴,适用于有意设计的序列或真实的加载循环。与过渡不同,关键帧不会自动根据变化后的终点重新调整。取消、反转或重新调整目标都需要显式编排。
- 只有在复杂度确有必要时,才使用现有的 Motion/动画库方案来处理手势物理效果、协调退出动画或共享布局。单靠 CSS 或 JavaScript 都无法保证在合成器上执行。
检查减少动态效果设置,并保留无障碍基础组件的焦点、键盘、隐藏内容和卸载生命周期。以下 Motion 示例使用 motion/react;应根据实际存在的包进行调整,不要悄然更改导入或依赖。
快速入门
当项目令牌未能明确选择时,可将以下内容作为起点:
- 该元素正在进入或退出吗? → 使用
ease-out - 屏幕上的元素正在移动吗? → 使用
ease-in-out - 这是悬停/颜色过渡吗? → 使用
ease - 这是高频控件吗? → 优先提供即时/静态反馈;只有确实有助于理解时才添加动态效果
缓动蓝图
ease-out(最常用)
用于用户发起的交互:下拉菜单、模态框、工具提示,以及任何进入或退出屏幕的元素。
/* Sorted weak to strong */
--ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
--ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);
--ease-out-quart: cubic-bezier(0.165, 0.84, 0.44, 1);
--ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1);
--ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
--ease-out-circ: cubic-bezier(0.075, 0.82, 0.165, 1);
为何有效:初始移动更快,能让用户迅速察觉反馈。元素会先“跳向”目标位置,然后逐渐停稳。
ease-in-out(用于移动)
当屏幕上已有的元素需要移动或变形时使用。模拟汽车先加速再刹车等自然运动。
/* Sorted weak to strong */
--ease-in-out-quad: cubic-bezier(0.455, 0.03, 0.515, 0.955);
--ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1);
--ease-in-out-quart: cubic-bezier(0.77, 0, 0.175, 1);
--ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1);
--ease-in-out-expo: cubic-bezier(1, 0, 0, 1);
--ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.15, 0.86);
ease(用于悬停效果)
用于悬停状态和颜色过渡。这种非对称曲线(开始较快,结束较慢)让轻柔的动画显得优雅。
transition: background-color 150ms ease;
linear(恒定速度或直接映射)
适用于恒定速度的运动、真实呈现时间/进度,或直接映射滚动/手势。对于应直接跟随输入的控件,不要添加平滑延迟。
ease-in(几乎从不使用)
避免使用启动缓慢、会延迟用户反馈的曲线。若需要有意呈现离场效果,或许应采用不同曲线;请保留现有交互语义并检查结果。
成对元素规则
协调相关元素,使它们看起来像一个整体。使用相同的缓动和时长是一个不错的起点;只要保留现有组件生命周期,有目的地让遮罩层和内容之间存在不对称动画也完全可行。
/* Both use the same timing */
.modal {
transition: transform 200ms ease-out;
}
.overlay {
transition: opacity 200ms ease-out;
}
时间与时长
时长指南
| 元素类型 | 时长 |
|---|---|
| 微交互 | 100-150ms |
| 标准 UI(工具提示、下拉菜单) | 150-250ms |
| 模态框、抽屉 | 200-300ms |
产品默认值,而非通用限制:
- 产品控件的动画应简短,通常控制在 100–300ms 内,不要使用弹跳或弹性过冲
- 较大的元素应比小元素动画得慢
- 退出动画可以比进入动画快约 20%
- 根据距离和任务频率调整时长;不要延迟可用内容的显示
- 较长的手势缓动或富有表现力的品牌动画序列,必须有经批准的简报支持,并结合实际情境进行检查。相邻控件不应默认采用此类动画
使用频率
确定用户看到动画的频率:
- 高频 → 不添加动画,或仅提供最少反馈
- 偶尔使用 → 使用标准动画
- 罕见/首次使用 → 可以更特别
例如,频繁打开的命令面板可能更适合立即显示。
何时使用动画
应使用动画:
- 用于保持空间一致性的进入/退出过渡
- 能从视觉连续性中受益的状态变化
- 对用户操作的响应(反馈)
- 能从惊喜感中受益的低频交互
不应使用动画:
- 会延迟响应的重复键盘导航或焦点移动
- 高频使用元素的悬停效果
- 静态反馈已足够清晰的高频控件
- 速度比流畅度更重要时
营销与产品:
- 营销:允许使用更复杂、时长更长的动画
- 产品:快速、有目的,绝不浮夸
弹簧动画
弹簧可以保留手势的动量,并支持重新定位目标。它只是一种选择,并不天然优于短过渡;基于时长和基于物理效果的配置表现不同。
何时使用弹簧
- 带有动量的拖动交互
- 可在动画过程中被中断的手势
- 经特别批准、且弹簧效果符合简报要求的表现性动画
配置
基于时长、不弹跳的产品示例(根据已安装的 API/令牌调整):
// Duration + bounce (easier to understand)
{ type: "spring", duration: 0.25, bounce: 0 }
传统物理配置:
// Mass, stiffness, damping (more complex)
{ type: "spring", mass: 1, stiffness: 100, damping: 20 }
弹跳指南
- 产品动画的基准效果不应包含弹跳或弹性过冲。
- 只有在经批准的简报支持时,手势/边界响应或品牌表现才可以使用克制的例外。“活泼”本身并不意味着可以到处添加弹跳效果。
- 在实际交互中评估所选参数;数值范围并不代表已获审美认可。确保反馈及时,并保留减少动态效果时的行为。
可中断性
某些弹簧实现会在重新设定目标时保留当前速度;请确认已安装的 API。CSS 过渡也支持交互式重新设定目标。不要将它们与从编写时起点重新播放关键帧时间线混为一谈。
在动画持续期间测试关闭/重新打开/关闭的操作。必须以最新意图为准,不能让过时的回调清除较新的状态。重新设定目标或卸载时,取消或使过时任务失效;并处理时长为零的减少动态效果路径,避免等待可能不会触发的事件。将状态清理保留在现有原语中。
布局动画(Framer Motion)
Motion 布局投影可以为尺寸/位置变化的视觉结果添加动画,其中包括 flex-direction 等离散布局变化。但它不会让这些 CSS 属性变成可直接插值的值。
layout 属性
向任意 motion.* 元素添加 layout,即可自动为布局变化添加动画:
<motion.div layout className="element" />
该库会测量支持的布局变化,并为其视觉效果添加动画。在实际使用场景中检查子元素变形、裁剪、滚动和性能;该属性并不保证动画流畅。
共享布局动画(layoutId)
连接两个独立元素,使其中一个变形为另一个:
// Tab highlight: only rendered for active tab
{activeTab === tab ? (
<motion.div layoutId="tab-indicator" className="highlight" />
) : null}
适用场景:标签页高亮、卡片 → 模态框展开、按钮 → 弹出框变形、垃圾桶交互(图片在容器之间移动)。
创意技巧: layoutId 可以营造错觉。反馈弹出框的“占位符”实际上是带有共享 layoutId 的 <span>,并非真正的文本区域占位符。它会从按钮文本变形为弹出框文本。
动态高度动画
当动态内容需要显式测量高度时,可复用项目中已有的测量工具;如果已安装,react-use-measure 是一种选择:
import useMeasure from "react-use-measure";
const [ref, bounds] = useMeasure();
<motion.div animate={{ height: bounds.height }}>
<div ref={ref}>{dynamicContent}</div>
</motion.div>
高度动画会影响布局。仅在空间连续性确有必要时使用,并在目标浏览器中测量;不要仅为这段代码添加依赖项。
AnimatePresence(深入)
模式
"sync"(默认):进入和退出动画同时播放"wait":退出动画完成后再开始进入动画(复制/勾选图标切换)"popLayout":立即将退出元素从布局流中移除(通常适用于变形 UI)
感知方向的过渡
使用 custom 属性向退出组件传递动态数据(其状态已过时):
<AnimatePresence mode="popLayout" custom={direction}>
<motion.div
key={step}
custom={direction}
initial="enter"
animate="center"
exit="exit"
variants={{
enter: (dir) => ({ x: dir > 0 ? 100 : -100, opacity: 0 }),
center: { x: 0, opacity: 1 },
exit: (dir) => ({ x: dir > 0 ? -100 : 100, opacity: 0 }),
}}
/>
</AnimatePresence>
键规则
- 始终为 AnimatePresence 中的动画元素添加
key属性 - 使用
initial={false}跳过挂载动画(图标切换、按钮状态) - 更改动画编排前,先在已安装的版本中复现快速切换缺陷。不要将历史版本固定为自动修复方案
动效值与 Hook
动效值可以在 React 渲染周期之外更新。这可以避免每帧更新 React 状态,但并不保证帧率。
useMotionValue:即时更新(手势)
const x = useMotionValue(0);
// Update via x.set(newValue), read via x.get()
<motion.div style={{ x }} />
适用于:直接跟踪手势(拖动距离 → 缩放),以及任何弹簧延迟会显得脱节的一对一映射。
useSpring:动画更新(跟随滞后)
const x = useSpring(0, { mass: 0.1, damping: 16, stiffness: 71 });
// x.set(newValue) animates to it with spring physics
适用于:光标跟随效果、动量效果,以及任何应落后于输入的效果。
useTransform:将一个值映射到另一个值
// Range mapping: y position [0, 300] → scale [1, 1.5]
const scale = useTransform(y, [0, 300], [1, 1.5]);
// Function form: format a value
const display = useTransform(angle, v => `${Math.round(v)}°`);
适用于:滚动联动效果、光标距离效果、值格式化。
useMotionTemplate:使用动效值进行字符串插值
const clipPath = useMotionTemplate`inset(0px ${percent}% 0px 0px)`;
<motion.div style={{ clipPath }} />
MotionConfig:为子树设置默认过渡
<MotionConfig transition={{ type: "spring", duration: 0.25, bounce: 0 }}>
{/* All children use this transition unless overridden */}
</MotionConfig>
编排(交错与排序)
仅在短序列有助于阅读顺序或实现已获批准的表现节奏时使用交错动画。确保无需等待序列完成,就能访问真实内容并执行操作。
CSS 交错(无需库):
.item { animation: fadeSlideIn 200ms ease-out both; }
.item:nth-child(1) { animation-delay: 0ms; }
.item:nth-child(2) { animation-delay: 50ms; }
.item:nth-child(3) { animation-delay: 100ms; }
/* etc. */
FM 交错:
const container = {
animate: { transition: { staggerChildren: 0.05 } }
};
const item = {
initial: { opacity: 0, y: 20 },
animate: { opacity: 1, y: 0 }
};
规则:
- 将延迟控制在较短范围(项目之间为 30-80ms)
- 计算总序列时长时,计入最后一个项目的动画时长:10 个项目,开始时间间隔为 80ms,动画时长为 200ms,总计需要 920ms;对于常规控件而言过慢
- 营销页面可以更复杂;产品 UI 应保持快速
通过动画速度展现品牌风格
时序可以强化已获批准的品牌方向:
- 快速: 即时或极快速反馈。
- 沉稳: 克制且富有表现力的过渡,同时不延误任务。
- 活泼: 范围有限的弹簧或变形效果;如需例外使用弹跳效果,必须有设计 brief 支持。
- 产品 UI 通常应保持快速,与品牌无关。
- 营销页面 适合通过动效展现品牌个性。
流畅界面(愿景)
当流畅的衔接能说明某个元素的来去时,它很有用。并非所有内容都必须变形,简单呈现有时反而更清晰。
- 共享布局动画是 Web 中最接近原生流畅效果的工具
- 将感知到的连续性与实际任务速度进行对照;不要假设动画能让延迟变得可以接受
- 在获准的设计决策中考虑连续性;不要仅为展示变形效果而重新排列现有 UI
- 文本变形(例如按钮标签变化)能以细微的方式突出状态变化
性能
当 transform 和 opacity 能表达预期变化时,优先使用它们来实现动画。它们通常能避免布局计算,也适合由合成器处理,但实际执行路径取决于浏览器、动画内容、库的实现方式和设备。CSS 声明或库属性并不能保证由 GPU 执行或达到特定帧率。请参阅 Motion 性能指南。
- 高度、宽度、内边距和外边距动画可能需要重新布局;只有在空间变化确有必要时才使用,并测量实际使用它的对象。
- 模糊/滤镜的开销取决于绘制区域、半径、设备和浏览器。不存在普遍安全的 20px 临界值。不要用模糊来掩盖尚未解决的过渡或状态缺陷。
- 将逐帧变化限制在局部。继承的自定义属性更新可能扩大样式计算范围;保留项目令牌,并在更改方案前检查受影响的后代元素。
- 当直接使用动态值或受支持的动画 API 就足够时,避免逐帧更新 React 状态。并非每次 React 渲染或每个 JavaScript 动画都会导致掉帧。
- 只有在观察到问题且测量结果支持时才添加
will-change,并将其限制在相关元素及所需时段内。它只是提示,会产生内存和堆叠上下文开销,并不会强制由 GPU 执行。请参阅 MDN will-change。
如果要声称性能有所提升,请记录实际使用的浏览器/设备、交互操作和性能跟踪。对于有充分理由的布局或模糊动画,请在桌面端和目标移动设备/WebKit 上进行检查;构建成功、变换后的静态画面或成功导入依赖,都不能作为此类证据。
无障碍
动画可能会让部分用户感到晕动或分心。
prefers-reduced-motion
通过现有 CSS 或库机制遵循此偏好。对于装饰性 CSS 动画,可以通过限定作用域的回退方案将其禁用:
.modal {
animation: fadeIn 200ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
.modal {
animation: none;
}
}
减少动态效果指南
减少动态效果应保留状态和反馈,但不一定要保留动画。静态变化是有效方案;如适用,也可以选择短暂淡入淡出,但它并非通用回退方案。
- 移除: 基于 transform 的移动、缩放、滑动和视差效果
- 保留: 可见状态、焦点和真实状态提示;使用静态反馈或适当的短暂淡入淡出
- 可选替代方案: 滑入 → 淡入,缩放 → 透明度变化,复杂效果 → 静态或简单效果
- 禁止视频/动画图像自动播放;改为显示播放按钮
- 停止装饰性循环动画,并显示选定的静态帧。仅设置负的动画延迟并不会暂停动画
Framer Motion 实现
选项 1:按组件使用 Hook
import { useReducedMotion } from "motion/react";
function Component({ isOpen }) {
const shouldReduceMotion = useReducedMotion();
const closedX = shouldReduceMotion ? 0 : "-100%";
return (
<motion.div animate={{
opacity: isOpen ? 1 : 0,
x: isOpen ? 0 : closedX
}} />
);
}
选项 2:配置现有子树
import { MotionConfig } from "motion/react";
// Honors the user preference for Motion transform/layout animation in this subtree
<MotionConfig reducedMotion="user">{children}</MotionConfig>
在限定范围内使用或扩展现有配置,不要重复创建应用级包装器。文档中注明的默认值为 "never";"user" 会禁用 transform/布局动画,而其他值仍可能播放动画。请分别检查透明度、颜色、时序以及任何 CSS/第三方循环动画。请参阅 MotionConfig 文档。这些代码片段仅描述动态效果,并非完整的无障碍模态框生命周期方案。
触控设备注意事项
/* Disable hover animations on touch devices */
@media (hover: hover) and (pointer: fine) {
.element:hover {
transform: scale(1.05);
}
}
触控设备会在点击时触发 hover,导致误报。
实用技巧
常见场景速查。详细实现请参阅 PRACTICAL-TIPS.md。
| 场景 | 解决方案 |
|---|---|
| 按压反馈缺失 | 优先保留静态反馈;只有在确有必要时才使用局部缩放 |
| 元素凭空出现 | 从 scale(0.95) 开始,而不是 scale(0) |
| 动画抖动 | 在测试提示之前,先检查布局、吸附和渲染 |
| 悬停导致闪烁 | 为子元素添加动画,而不是父元素 |
| 弹出层缩放原点不正确 | 将 transform-origin 设置为触发位置 |
| 依次显示的工具提示感觉缓慢 | 第一个工具提示之后跳过延迟和动画 |
| 小按钮难以点击 | 保留共享的目标尺寸基础样式;避免命中区域重叠 |
| 仍然感觉不对 | 记录并重新触发,然后检查状态和几何形状,再添加效果 |
| 移动设备上触发悬停 | 使用 @media (hover: hover) and (pointer: fine) |
缓动决策流程图
元素是否正在进入或离开视口? ├── 是 → ease-out └── 否 ├── 它是否正在屏幕上移动或变形? │ └── 是 → ease-in-out └── 这是悬停状态变化吗? ├── 是 → ease └── 这是持续运动吗? ├── 是 → linear └── 默认 → ease-out
参考文件
- PRACTICAL-TIPS.md:只阅读相关的实现方案。
- Transitions candidate:仅在某个固定方案能解决特定的运动效果缺口时查阅;此方案经过评估,但未采纳或安装。