关于 web-animation-design
web-animation-design 是面向 Web 界面动效设计与评审的参考技能,内容基于 Emil Kowalski 的 animations.dev 课程。它涵盖缓动、时长、性能、弹簧动画和减少动态效果的无障碍支持,并规定评审使用表格。
主要功能
- 01按交互类型选择缓动
- 02界面动效时长与频率指引
- 03弹簧动画配置建议
- 04CSS 与 JavaScript 性能指引
- 05减少动态效果的无障碍实践
- 06评审必须使用对照表
适用场景
- 01为模态框或下拉菜单选择缓动
- 02设置高频界面交互的动效时长
- 03审查动画性能方案
- 04添加减少动态效果支持
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vercel-labs/ open-agents --skill web-animation-design安装后在 Claude Code 中输入/web-animation-design确认已加载。
附带文件PRACTICAL-TIPS.md
详细介绍
Web 动画设计
这是一份全面指南,介绍如何创建自然得体的动画,内容基于 Emil Kowalski 的《Animations on the Web》课程。
初始响应
首次调用此技能且未提出具体问题时,只回复:
我已准备好根据 Emil Kowalski 的 animations.dev 课程为你提供动画方面的帮助。
在用户提问之前,不要提供任何其他信息。
评审格式(必需)
评审动画时,必须使用 Markdown 表格。不要将“Before:”和“After:”分别写在单独的行中。始终输出实际的 Markdown 表格,如下所示:
| 修改前 | 修改后 |
|---|---|
transform: scale(0) | transform: scale(0.95) |
animation: fadeIn 400ms ease-in | animation: fadeIn 200ms ease-out |
| 不支持减少动态效果 | @media (prefers-reduced-motion: reduce) {...} |
错误格式(绝不要这样写):
Before: transform: scale(0)
After: transform: scale(0.95)
────────────────────────────
Before: 400ms duration
After: 200ms
正确格式:一个包含“修改前”和“修改后”列的 Markdown 表格,每个问题占一行。
快速入门
每次决定如何设计动画,都要先问自己:
- 这个元素是要进入还是退出? → 使用
ease-out - 屏幕上的元素正在移动吗? → 使用
ease-in-out - 这是悬停/颜色过渡吗? → 使用
ease - 用户每天会看到它 100 次以上吗? → 不要添加动画
缓动设计指南
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(避免用于 UI)
仅用于:
- 匀速动画(跑马灯、滚动字幕)
- 时间可视化(按住删除进度指示器)
线性动画用于交互元素时,会显得机械且不自然。
ease-in(几乎绝不使用)
**避免用于 UI 动画。**缓慢的开始会延迟视觉反馈,让界面显得迟钝。
成对元素规则
一起动效的元素必须使用相同的缓动方式和时长。模态框和遮罩层、工具提示和箭头、抽屉和背景——如果它们作为一个整体移动,感觉上也应该是一个整体。
/* Both use the same timing */
.modal {
transition: transform 200ms ease-out;
}
.overlay {
transition: opacity 200ms ease-out;
}
时间与时长
时长指南
| 元素类型 | 时长 |
|---|---|
| 微交互 | 100-150ms |
| 标准 UI(工具提示、下拉菜单) | 150-250ms |
| 模态框、抽屉 | 200-300ms |
规则:
- UI 动画应控制在 300ms 以内
- 较大的元素比小元素动画更慢
- 退出动画可以比进入动画快约 20%
- 时长应与移动距离匹配——移动距离越长,时长越长
使用频率
判断用户会多频繁地看到该动画:
- 每天 100 次以上 → 不添加动画(或大幅减少动画)
- 偶尔使用 → 使用标准动画
- 很少使用/首次使用 → 可以设计得更特别
**示例:**Raycast 从不使用动画,因为用户每天会打开它数百次。
何时使用动画
适合使用动画:
- 进入/退出过渡,以保持空间一致性
- 能通过视觉连续性改善体验的状态变化
- 对用户操作的响应(反馈)
- 不常用的交互,且动画能增添乐趣
不适合使用动画:
- 由键盘触发的操作
- 常用元素的悬停效果
- 用户每天交互 100 次以上的任何内容
- 速度比流畅度更重要的场景
营销与产品:
- 营销:可以更精致,时长也可以更长
- 产品:快速、有明确目的,绝不浮夸
弹簧动画
弹簧动画感觉更自然,因为它们没有固定时长,而是模拟真实物理效果。
何时使用弹簧动画
- 带有惯性的拖拽交互
- 应该感觉“鲜活”的元素(灵动岛)
- 动画过程中可以中断的手势
- 自然、有趣的界面
配置
Apple 的做法(推荐):
// Duration + bounce (easier to understand)
{ type: "spring", duration: 0.5, bounce: 0.2 }
传统物理效果:
// Mass, stiffness, damping (more complex)
{ type: "spring", mass: 1, stiffness: 100, damping: 10 }
回弹指南
- 在大多数 UI 场景中,避免回弹
- 对拖拽关闭和有趣的交互,使用回弹
- 使用回弹时保持轻微(0.1-0.3)
可中断性
被中断时,弹簧动画会保留速度;CSS 动画则会从零重新开始。因此,对于用户可能在运动过程中改变操作的手势,弹簧动画是理想选择。
性能
黄金法则
只为 transform 和 opacity 添加动画。它们会跳过布局和绘制阶段,完全由 GPU 执行。
避免为以下属性添加动画:
padding、margin、height、width(会触发布局)- 超过 20px 的
blur滤镜(开销大,在 Safari 中尤其如此) - 深层组件树中的 CSS 变量
优化技巧
/* Force GPU acceleration */
.animated-element {
will-change: transform;
}
React 专属:
- 尽可能在 React 的渲染周期之外执行动画
- 使用 refs 直接更新样式,而不是使用状态
- 每一帧都重新渲染 = 丢帧
Framer Motion:
// Hardware accelerated (transform as string)
<motion.div animate={{ transform: "translateX(100px)" }} />
// NOT hardware accelerated (more readable)
<motion.div animate={{ x: 100 }} />
CSS 与 JavaScript
- CSS 动画在主线程之外运行(负载较高时更流畅)
- JS 动画(Framer Motion、React Spring)使用
requestAnimationFrame - CSS 更适合简单、预先设定的动画
- JS 更适合动态、可中断的动画
无障碍
动画可能会让部分用户感到晕动或分心。
prefers-reduced-motion
每次添加动画时,也要添加媒体查询来禁用动画:
.modal {
animation: fadeIn 200ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
.modal {
animation: none;
}
}
减少动态效果指南
- 每个动画元素都需要单独的
prefers-reduced-motion媒体查询 - 设置
animation: none或transition: none(不要使用!important) opacity或颜色动画也不例外——禁用所有动画- 显示播放按钮,而不是自动播放视频
Framer Motion 实现
import { useReducedMotion } from "framer-motion";
function Component() {
const shouldReduceMotion = useReducedMotion();
return (
<motion.div
initial={shouldReduceMotion ? false : { opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
/>
);
}
触控设备注意事项
/* Disable hover animations on touch devices */
@media (hover: hover) and (pointer: fine) {
.element:hover {
transform: scale(1.05);
}
}
触控设备会在轻触时触发悬停效果,从而造成误触。
实用技巧
常见场景的快速参考。详细实现请参见 PRACTICAL-TIPS.md。
| 场景 | 解决方案 |
|---|---|
| 让按钮响应更灵敏 | 在 :active 时添加 transform: scale(0.97) |
| 元素凭空出现 | 从 scale(0.95) 开始,而不是 scale(0) |
| 动画抖动 | 添加 will-change: transform |
| 悬停时出现闪烁 | 为子元素添加动画,而不是父元素 |
| 弹出层从错误的位置缩放 | 将 transform-origin 设为触发位置 |
| 连续出现的工具提示感觉很慢 | 第一个工具提示之后跳过延迟/动画 |
| 小按钮难以点击 | 使用最小 44px 的点击区域(伪元素) |
| 仍然感觉不对 | 添加轻微模糊(低于 20px)来掩饰 |
| 在移动设备上触发悬停 | 使用 @media (hover: hover) and (pointer: fine) |
缓动选择流程图
元素是否正在进入或离开视口? ├── 是 → ease-out └── 否 ├── 元素是否正在屏幕上移动或变形? │ └── 是 → ease-in-out └── 是否为悬停状态变化? ├── 是 → ease └── 是否为匀速运动? ├── 是 → linear └── 默认 → ease-out
参考文件
- PRACTICAL-TIPS.md - 常见动画场景的详细实现