web-animation-design

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Web 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:
BeforeAfter
transform: scale(0)
transform: scale(0.95)
animation: fadeIn 400ms ease-in
animation: fadeIn 200ms ease-out
No reduced motion support
@media (prefers-reduced-motion: reduce) {...}
Wrong format (never do this):
Before: transform: scale(0)
After: transform: scale(0.95)
────────────────────────────
Before: 400ms duration
After: 200ms
Correct format: A single markdown table with | Before | After | columns, one row per issue.
评审动画时,必须使用Markdown表格。不要使用分开展示“Before:”和“After:”的列表。始终输出如下格式的Markdown表格:
BeforeAfter
transform: scale(0)
transform: scale(0.95)
animation: fadeIn 400ms ease-in
animation: fadeIn 200ms ease-out
No reduced motion support
@media (prefers-reduced-motion: reduce) {...}
错误格式(请勿使用):
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:
  1. Is this element entering or exiting? → Use
    ease-out
  2. Is an on-screen element moving? → Use
    ease-in-out
  3. Is this a hover/color transition? → Use
    ease
  4. Will users see this 100+ times daily? → Don't animate it
所有动画决策都从以下问题开始:
  1. 该元素是入场还是退场? → 使用
    ease-out
  2. 屏幕内的元素是否需要移动? → 使用
    ease-in-out
  3. 这是 hover/颜色过渡效果吗? → 使用
    ease
  4. 用户每天会看到这个动画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 TypeDuration
Micro-interactions100-150ms
Standard UI (tooltips, dropdowns)150-250ms
Modals, drawers200-300ms
Page transitions300-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
transform
and
opacity
. These skip layout and paint stages, running entirely on the GPU.
Avoid animating:
  • padding
    ,
    margin
    ,
    height
    ,
    width
    (trigger layout)
  • blur
    filters above 20px (expensive, especially Safari)
  • CSS variables in deep component trees
仅对
transform
opacity
属性添加动画。这两个属性会跳过布局和绘制阶段,完全在GPU上运行。
避免为以下属性添加动画:
  • padding
    margin
    height
    width
    (会触发布局重排)
  • 超过20px的
    blur
    滤镜(性能开销大,尤其是在Safari中)
  • 深层组件树中的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
    prefers-reduced-motion
    media query
  • Set
    animation: none
    or
    transition: none
    (no
    !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.
ScenarioSolution
Make buttons feel responsiveAdd
transform: scale(0.97)
on
:active
Element appears from nowhereStart from
scale(0.95)
, not
scale(0)
Shaky/jittery animationsAdd
will-change: transform
Hover causes flickerAnimate child element, not parent
Popover scales from wrong pointSet
transform-origin
to trigger location
Sequential tooltips feel slowSkip delay/animation after first tooltip
Small buttons hard to tapUse 44px minimum hit area (pseudo-element)
Something still feels offAdd subtle blur (under 20px) to mask it
Hover triggers on mobileUse
@media (hover: hover) and (pointer: fine)
常见场景的快速参考。详细实现请查看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)

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 - 常见动画场景的详细实现方案