web-animation-design
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWeb Animation Design
Web 动画设计
A comprehensive guide for creating animations that feel right, based on Emil Kowalski's "Animations on the Web" course.
本指南基于Emil Kowalski的《Web动画》课程,是创建自然流畅动画的综合指南。
Initial Response
初始回复
When this skill is first invoked without a specific question, respond only with:
I'm ready to help you with animations based on Emil Kowalski's animations.dev course.
Do not provide any other information until the user asks a question.
当首次调用此技能且用户未提出具体问题时,仅回复:
我已准备好基于Emil Kowalski的animations.dev课程为你提供动画相关帮助。
在用户提出问题前,不要提供任何其他信息。
Review Format (Required)
评审格式(必填)
When reviewing animations, you MUST use a markdown table. Do NOT use a list with "Before:" and "After:" on separate lines. Always output an actual markdown table like this:
| Before | After |
|---|---|
| |
| |
| No reduced motion support | |
Wrong format (never do this):
Before: transform: scale(0)
After: transform: scale(0.95)
────────────────────────────
Before: 400ms duration
After: 200msCorrect format: A single markdown table with | Before | After | columns, one row per issue.
评审动画时,必须使用Markdown表格。不要使用分开展示“Before:”和“After:”的列表。始终输出如下格式的Markdown表格:
| Before | After |
|---|---|
| |
| |
| No reduced motion support | |
错误格式(请勿使用):
Before: transform: scale(0)
After: transform: scale(0.95)
────────────────────────────
Before: 400ms duration
After: 200ms正确格式:包含| Before | After |列的单个Markdown表格,每个问题占一行。
Quick Start
快速入门
Every animation decision starts with these questions:
- Is this element entering or exiting? → Use
ease-out - Is an on-screen element moving? → Use
ease-in-out - Is this a hover/color transition? → Use
ease - Will users see this 100+ times daily? → Don't animate it
所有动画决策都从以下问题开始:
- 该元素是入场还是退场? → 使用
ease-out - 屏幕内的元素是否需要移动? → 使用
ease-in-out - 这是 hover/颜色过渡效果吗? → 使用
ease - 用户每天会看到这个动画100次以上吗? → 不要添加动画
The Easing Blueprint
缓动蓝图
ease-out (Most Common)
ease-out(最常用)
Use for user-initiated interactions: dropdowns, modals, tooltips, any element entering or exiting the screen.
css
/* 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);Why it works: Acceleration at the start creates an instant, responsive feeling. The element "jumps" toward its destination then settles in.
适用于用户触发的交互:下拉菜单、模态框、提示框,以及任何入场或退场的元素。
css
/* 从弱到强排序 */
--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 (For Movement)
ease-in-out(适用于移动效果)
Use when elements already on screen need to move or morph. Mimics natural motion like a car accelerating then braking.
css
/* 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);适用于屏幕内元素需要移动或变形的场景。模拟汽车加速再刹车的自然运动。
css
/* 从弱到强排序 */
--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 (For Hover Effects)
ease(适用于悬停效果)
Use for hover states and color transitions. The asymmetrical curve (faster start, slower end) feels elegant for gentle animations.
css
transition: background-color 150ms ease;适用于悬停状态和颜色过渡。不对称的曲线(起始快、结束慢)为柔和的动画增添优雅感。
css
transition: background-color 150ms ease;linear (Avoid in UI)
linear(UI中避免使用)
Only use for:
- Constant-speed animations (marquees, tickers)
- Time visualization (hold-to-delete progress indicators)
Linear feels robotic and unnatural for interactive elements.
仅适用于:
- 匀速动画(跑马灯、滚动提示)
- 时间可视化(长按删除的进度指示器)
线性动画在交互元素中会显得机械、不自然。
ease-in (Almost Never)
ease-in(几乎从不使用)
Avoid for UI animations. Makes interfaces feel sluggish because the slow start delays visual feedback.
UI动画中请避免使用。缓慢的起始会延迟视觉反馈,让界面显得迟钝。
Paired Elements Rule
配对元素规则
Elements that animate together must use the same easing and duration. Modal + overlay, tooltip + arrow, drawer + backdrop—if they move as a unit, they should feel like a unit.
css
/* Both use the same timing */
.modal { transition: transform 200ms ease-out; }
.overlay { transition: opacity 200ms ease-out; }一起动画的元素必须使用相同的缓动效果和时长。模态框+遮罩层、提示框+箭头、侧边栏+背景层——如果它们作为一个整体移动,就应该给人一个整体的感受。
css
/* 两者使用相同的时序 */
.modal { transition: transform 200ms ease-out; }
.overlay { transition: opacity 200ms ease-out; }Timing and Duration
时序与时长
Duration Guidelines
时长指南
| Element Type | Duration |
|---|---|
| Micro-interactions | 100-150ms |
| Standard UI (tooltips, dropdowns) | 150-250ms |
| Modals, drawers | 200-300ms |
| Page transitions | 300-400ms |
Rule: UI animations should stay under 300ms. Larger elements animate slower than smaller ones.
| 元素类型 | 时长范围 |
|---|---|
| 微交互 | 100-150ms |
| 标准UI元素(提示框、下拉菜单) | 150-250ms |
| 模态框、侧边栏 | 200-300ms |
| 页面过渡 | 300-400ms |
规则: UI动画时长应控制在300ms以内。较大的元素动画速度应比较小元素慢。
The Frequency Principle
频率原则
Determine how often users will see the animation:
- 100+ times/day → No animation (or drastically reduced)
- Occasional use → Standard animation
- Rare/first-time → Can add delight
Example: Raycast never animates its menu toggle because users open it hundreds of times daily.
判断用户看到该动画的频率:
- 每天100次以上 → 不添加动画(或大幅简化)
- 偶尔使用 → 标准动画
- 罕见/首次使用 → 可添加趣味效果
示例: Raycast从不为其菜单切换添加动画,因为用户每天会打开数百次。
When to Animate
何时添加动画
Do animate:
- Enter/exit transitions for spatial consistency
- State changes that benefit from visual continuity
- Responses to user actions (feedback)
- Rarely-used interactions where delight adds value
Don't animate:
- Keyboard-initiated actions
- Hover effects on frequently-used elements
- Anything users interact with 100+ times daily
- When speed matters more than smoothness
Marketing vs. Product:
- Marketing: More elaborate, longer durations allowed
- Product: Fast, purposeful, never frivolous
建议添加动画的场景:
- 入场/退场过渡,保证空间一致性
- 能从视觉连续性中获益的状态变化
- 对用户操作的反馈(交互反馈)
- 罕见交互场景,趣味效果能提升体验
建议不添加动画的场景:
- 键盘触发的操作
- 高频使用元素的悬停效果
- 用户每天交互100次以上的元素
- 速度比流畅度更重要的场景
营销页面 vs 产品页面:
- 营销页面:允许更复杂、时长更长的动画
- 产品页面:动画应快速、有明确目的,绝不冗余
Spring Animations
Spring动画
Springs feel more natural because they don't have fixed durations—they simulate real physics.
Spring动画更自然,因为它们没有固定时长——模拟真实物理效果。
When to Use Springs
何时使用Spring动画
- Drag interactions with momentum
- Elements that should feel "alive" (Dynamic Island)
- Gestures that can be interrupted mid-animation
- Organic, playful interfaces
- 带有动量的拖拽交互
- 需要给人“鲜活”感的元素(如Dynamic Island)
- 可能在动画中途被打断的手势操作
- 有机、趣味化的界面
Configuration
配置方式
Apple's approach (recommended):
js
// Duration + bounce (easier to understand)
{ type: "spring", duration: 0.5, bounce: 0.2 }Traditional physics:
js
// Mass, stiffness, damping (more complex)
{ type: "spring", mass: 1, stiffness: 100, damping: 10 }苹果推荐方案(更易理解):
js
// 时长 + 弹跳效果
{ type: "spring", duration: 0.5, bounce: 0.2 }传统物理参数配置(更复杂):
js
// 质量、刚度、阻尼
{ type: "spring", mass: 1, stiffness: 100, damping: 10 }Bounce Guidelines
弹跳效果指南
- Avoid bounce in most UI contexts
- Use bounce for drag-to-dismiss, playful interactions
- Keep bounce subtle (0.1-0.3) when used
- 大多数UI场景避免使用弹跳效果
- 可使用弹跳效果的场景:拖拽关闭、趣味化交互
- 使用时保持弹跳效果柔和(0.1-0.3)
Interruptibility
可中断性
Springs maintain velocity when interrupted—CSS animations restart from zero. This makes springs ideal for gestures users might change mid-motion.
Spring动画在被打断时会保持速度——而CSS动画会从零重新开始。这使得Spring动画非常适合用户可能中途改变的手势操作。
Performance
性能优化
The Golden Rule
黄金法则
Only animate and . These skip layout and paint stages, running entirely on the GPU.
transformopacityAvoid animating:
- ,
padding,margin,height(trigger layout)width - filters above 20px (expensive, especially Safari)
blur - CSS variables in deep component trees
仅对和属性添加动画。这两个属性会跳过布局和绘制阶段,完全在GPU上运行。
transformopacity避免为以下属性添加动画:
- 、
padding、margin、height(会触发布局重排)width - 超过20px的滤镜(性能开销大,尤其是在Safari中)
blur - 深层组件树中的CSS变量
Optimization Techniques
优化技巧
css
/* Force GPU acceleration */
.animated-element {
will-change: transform;
}React-specific:
- Animate outside React's render cycle when possible
- Use refs to update styles directly instead of state
- Re-renders on every frame = dropped frames
Framer Motion:
jsx
// Hardware accelerated (transform as string)
<motion.div animate={{ transform: "translateX(100px)" }} />
// NOT hardware accelerated (more readable)
<motion.div animate={{ x: 100 }} />css
/* 强制开启GPU加速 */
.animated-element {
will-change: transform;
}React专属优化:
- 尽可能在React渲染周期外执行动画
- 使用refs直接更新样式,而非通过状态
- 每帧都触发重渲染会导致丢帧
Framer Motion注意事项:
jsx
// 硬件加速(transform为字符串形式)
<motion.div animate={{ transform: "translateX(100px)" }} />
// 不支持硬件加速(可读性更高)
<motion.div animate={{ x: 100 }} />CSS vs. JavaScript
CSS vs JavaScript动画
- CSS animations run off main thread (smoother under load)
- JS animations (Framer Motion, React Spring) use
requestAnimationFrame - CSS better for simple, predetermined animations
- JS better for dynamic, interruptible animations
- CSS动画在主线程外运行(负载高时更流畅)
- JS动画(Framer Motion、React Spring)使用
requestAnimationFrame - CSS更适合简单、预先确定的动画
- JS更适合动态、可中断的动画
Accessibility
可访问性
Animations can cause motion sickness or distraction for some users.
动画可能会导致部分用户出现晕动症或注意力分散。
prefers-reduced-motion
prefers-reduced-motion
Whenever you add an animation, also add a media query to disable it:
css
.modal {
animation: fadeIn 200ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
.modal {
animation: none;
}
}添加动画时,同时添加媒体查询以支持关闭动画:
css
.modal {
animation: fadeIn 200ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
.modal {
animation: none;
}
}Reduced Motion Guidelines
减少动效的指南
- Every animated element needs its own media query
prefers-reduced-motion - Set or
animation: none(notransition: none)!important - No exceptions for opacity or color - disable all animations
- Show play buttons instead of autoplay videos
- 每个动画元素都需要单独的媒体查询
prefers-reduced-motion - 设置或
animation: none(不要使用transition: none)!important - 无例外:即使是透明度或颜色动画也需要禁用
- 用播放按钮替代自动播放的视频
Framer Motion Implementation
Framer Motion实现方式
jsx
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 }}
/>
);
}jsx
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 }}
/>
);
}Touch Device Considerations
触控设备考量
css
/* Disable hover animations on touch devices */
@media (hover: hover) and (pointer: fine) {
.element:hover {
transform: scale(1.05);
}
}Touch devices trigger hover on tap, causing false positives.
css
/* 在触控设备上禁用悬停动画 */
@media (hover: hover) and (pointer: fine) {
.element:hover {
transform: scale(1.05);
}
}触控设备会在点击时触发悬停状态,导致误触发。
Practical Tips
实用技巧
Quick reference for common scenarios. See PRACTICAL-TIPS.md for detailed implementations.
| Scenario | Solution |
|---|---|
| Make buttons feel responsive | Add |
| Element appears from nowhere | Start from |
| Shaky/jittery animations | Add |
| Hover causes flicker | Animate child element, not parent |
| Popover scales from wrong point | Set |
| Sequential tooltips feel slow | Skip delay/animation after first tooltip |
| Small buttons hard to tap | Use 44px minimum hit area (pseudo-element) |
| Something still feels off | Add subtle blur (under 20px) to mask it |
| Hover triggers on mobile | Use |
常见场景的快速参考。详细实现请查看PRACTICAL-TIPS.md。
| 场景 | 解决方案 |
|---|---|
| 让按钮更具响应感 | 在 |
| 元素从无到有出现 | 从 |
| 动画抖动/卡顿 | 添加 |
| 悬停导致闪烁 | 为子元素添加动画,而非父元素 |
| 弹出层缩放原点错误 | 设置 |
| 连续提示框感觉缓慢 | 第一个提示框后跳过延迟/动画 |
| 小按钮难以点击 | 使用44px最小点击区域(伪元素) |
| 动画仍感觉不协调 | 添加轻微模糊(小于20px)来掩盖 |
| 移动端触发悬停效果 | 使用 |
Easing Decision Flowchart
缓动效果决策流程图
Is the element entering or exiting the viewport?
├── Yes → ease-out
└── No
├── Is it moving/morphing on screen?
│ └── Yes → ease-in-out
└── Is it a hover change?
├── Yes → ease
└── Is it constant motion?
├── Yes → linear
└── Default → ease-out
元素是否要进入或退出视口?
├── 是 → ease-out
└── 否
├── 元素是否在屏幕内移动/变形?
│ └── 是 → ease-in-out
└── 是否为悬停变化?
├── 是 → ease
└── 是否为匀速运动?
├── 是 → linear
└── 默认 → ease-out
Reference Files
参考文件
- PRACTICAL-TIPS.md - Detailed implementations for common animation scenarios
- PRACTICAL-TIPS.md - 常见动画场景的详细实现方案