Web Animation Design
⚠️ Creative Pack — NOT auto-apply
This skill is part of the creative pack. Use when the task explicitly involves animation, motion, or interaction feel. Do NOT load this for general UI building — the motion reference in design-review covers baseline motion quality.
Use when: user asks about animations, easing, springs, transitions, interaction feel, or "make it smooth."
Skip when: building standard UI where the motion reference in design-review is sufficient.
A comprehensive guide for creating animations that feel right, based on Emil Kowalski's "Animations on the Web" course.
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.
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 |
|---|
| INLINECODE0 | INLINECODE1 |
| INLINECODE2 |
animation: fadeIn 200ms ease-out |
| No reduced motion support |
@media (prefers-reduced-motion: reduce) {...} |
Wrong format (never do this):
CODEBLOCK0
Correct format: A single markdown table with | Before | After | columns, one row per issue.
Decision Tree: What Tool Do I Use?
CODEBLOCK1
Always check prefers-reduced-motion. No exceptions.
Quick Start
Every animation decision starts with these questions:
- 1. Is this element entering or exiting? → Use INLINECODE6
- Is an on-screen element moving? → Use INLINECODE7
- Is this a hover/color transition? → Use INLINECODE8
- Will users see this 100+ times daily? → Don't animate it
The Easing Blueprint
ease-out (Most Common)
Use for user-initiated interactions: dropdowns, modals, tooltips, any element entering or exiting the screen.
CODEBLOCK2
Why it works: Acceleration at the start creates an instant, responsive feeling. The element "jumps" toward its destination then settles in.
ease-in-out (For Movement)
Use when elements already on screen need to move or morph. Mimics natural motion like a car accelerating then braking.
CODEBLOCK3
ease (For Hover Effects)
Use for hover states and color transitions. The asymmetrical curve (faster start, slower end) feels elegant for gentle animations.
CODEBLOCK4
linear (Avoid in 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)
Avoid for UI animations. Makes interfaces feel sluggish because the slow start delays visual feedback.
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.
CODEBLOCK5
Timing and Duration
Duration Guidelines
| Element Type | Duration |
|---|
| Micro-interactions | 100-150ms |
| Standard UI (tooltips, dropdowns) |
150-250ms |
| Modals, drawers | 200-300ms |
Rules:
- - UI animations should stay under 300ms
- Larger elements animate slower than smaller ones
- Exit animations can be ~20% faster than entrance
- Match duration to distance - longer travel = longer duration
The Frequency
Determine how often users will see the animation:
- - 100+ times/day → No animation (or drastically reduced)
- Occasional use → Standard animation
- Rare/first-time → Can be more special
Example: Raycast never animates because users open it hundreds of times a day.
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
Spring Animations
Springs feel more natural because they don't have fixed durations—they simulate real physics.
When to Use Springs
- - Drag interactions with momentum
- Elements that should feel "alive" (Dynamic Island)
- Gestures that can be interrupted mid-animation
- Organic, playful interfaces
Configuration
Apple's approach (recommended):
CODEBLOCK6
Traditional physics:
CODEBLOCK7
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
Interruptibility
Springs maintain velocity when interrupted—CSS animations restart from zero. This makes springs ideal for gestures users might change mid-motion.
Layout Animations (Framer Motion)
The most powerful FM feature. Lets you animate properties CSS can't: flex-direction, justify-content, position changes.
The layout Prop
Add
layout to any
motion.* element to auto-animate layout changes:
<motion.div layout className="element" />
When this element's size or position changes (due to state, content, siblings), FM animates it smoothly. No manual measurement needed.
Shared Layout Animations (layoutId)
Connect two separate elements so one morphs into the other:
// Tab highlight — only rendered for active tab
{activeTab === tab ? (
<motion.div layoutId="tab-indicator" className="highlight" />
) : null}
Use cases: tab highlights, card → modal expansion, button → popover morph, trash interaction (images move between containers).
Creative trick: layoutId creates illusions. The feedback popover's "placeholder" is actually a <span> with a shared layoutId — not a real textarea placeholder. It morphs from button text to popover text.
Dynamic Height Animation
FM can't animate
auto to
auto. Use
react-use-measure:
CODEBLOCK10
AnimatePresence (Deep)
Modes
- -
"sync" (default) — enter and exit play simultaneously - INLINECODE22 — exit completes before enter starts (copy/check icon swap)
- INLINECODE23 — removes exiting element from layout flow immediately (often the right choice for morphing UIs)
Direction-Aware Transitions
Use the
custom prop to pass dynamic data to exiting components (whose state is stale):
CODEBLOCK11
Key Rules
- - Always add
key prop on animated elements inside AnimatePresence - Use
initial={false} to skip mount animation (icon swaps, button states) - Known bug: rapid switching can show both elements — pin to FM v11.0.10 if hit
Motion Values & Hooks
Motion values update outside React's render cycle — no re-renders, 60fps.
useMotionValue — Instant updates (gestures)
const x = useMotionValue(0);
// Update via x.set(newValue), read via x.get()
<motion.div style={{ x }} />
Use for: direct gesture tracking (drag distance → scale), any 1:1 mapping where spring lag would feel disconnected.
useSpring — Animated updates (follow-behind)
const x = useSpring(0, { mass: 0.1, damping: 16, stiffness: 71 });
// x.set(newValue) animates to it with spring physics
Use for: cursor followers, momentum effects, anything that should trail behind input.
useTransform — Map one value to another
// 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)}°`);
Use for: scroll-linked effects, cursor-distance effects, value formatting.
useMotionTemplate — String interpolation with motion values
CODEBLOCK15
MotionConfig — Default transitions for a subtree
CODEBLOCK16
Orchestration (Stagger & Sequencing)
Staggered entrance animations create a wave effect. Trial and error until it feels right — no formula.
CSS stagger (no library):
CODEBLOCK17
FM stagger:
CODEBLOCK18
Rules:
- - Keep delays small (30-80ms between items)
- Cap total sequence time — 10 items at 80ms each = 800ms, too slow
- Marketing pages can be more elaborate; product UI should be fast
Brand Expression Through Animation Speed
Animation timing IS brand identity:
- - Speed brand (Vercel): instant or very fast, minimal easing. "We don't waste your time."
- Premium brand (Stripe): slower, more deliberate.
ease curve (not ease-out) for elegance. - Playful brand (Family): springs with subtle bounce, fluid morphing.
- Product UI should generally feel fast regardless of brand.
- Marketing pages are where you express brand personality through motion.
Fluid Interfaces (Aspirational)
The north star: nothing "appears" or "disappears" — everything morphs. Family (iOS) is the gold standard.
- - Shared layout animations are the web's closest tool to native fluidity
- Fluid motion improves perceived performance (feels faster even with same load time)
- Think about animations BEFORE designing the UI — position elements to enable seamless transitions
- Currently hard on web, but the direction everything is heading
- Text morphing (e.g., button label changes) highlights state changes subtly
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) - INLINECODE39 filters above 20px (expensive, especially Safari)
- CSS variables in deep component trees
Optimization Techniques
CODEBLOCK19
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:
CODEBLOCK20
CSS vs. JavaScript
- - CSS animations run off main thread (smoother under load)
- JS animations (Framer Motion, React Spring) use INLINECODE40
- CSS better for simple, predetermined animations
- JS better for dynamic, interruptible animations
- Combine both: CSS for simple/perf-critical animations, FM for complex/layout/springs
CSS Variables Gotcha
CSS variables are inheritable — changing one causes style recalc for ALL children. In deep component trees (20+ items), this kills drag/scroll performance.
CODEBLOCK21
Transform Shift Fix
GPU/CPU handoff can cause 1px shift at animation start/end:
CODEBLOCK22
Accessibility
Animations can cause motion sickness or distraction for some users.
prefers-reduced-motion
Whenever you add an animation, also add a media query to disable it:
CODEBLOCK23
Reduced Motion Guidelines
Reduced motion ≠ no motion. Animations help users understand UI. Removing all motion hurts comprehension.
- - Remove: transform-based movement, scaling, sliding, parallax
- Keep: opacity fades, color transitions, background changes
- Replace: slide-in → fade-in, scale → opacity, complex → simple
- Disable autoplay on videos/animated images; show play buttons instead
- For looping hero animations: pause on a good frame via INLINECODE41
Framer Motion Implementation
Option 1: Per-component hook
CODEBLOCK24
Option 2: App-wide wrapper (recommended)
import { MotionConfig } from "motion/react";
// Wraps your entire app — FM auto-reduces to opacity/backgroundColor only
<MotionConfig reducedMotion="user">{children}</MotionConfig>
Note: default is
"never" — you must set this yourself.
Touch Device Considerations
CODEBLOCK26
Touch devices trigger hover on tap, causing false positives.
Practical Tips
Quick reference for common scenarios. See PRACTICAL-TIPS.md for detailed implementations.
| Scenario | Solution |
|---|
| Make buttons feel responsive | Add transform: scale(0.97) on INLINECODE44 |
| Element appears from nowhere |
Start from
scale(0.95), not
scale(0) |
| Shaky/jittery animations | Add
will-change: transform |
| Hover causes flicker | Animate child element, not parent |
| Popover scales from wrong point | Set
transform-origin to trigger location |
| 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
@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
Reference Files
Web 动画设计
⚠️ 创意包 — 非自动应用
此技能属于创意包的一部分。当任务明确涉及动画、动效或交互感受时使用。不要为一般的 UI 构建加载此技能——设计评审中的动效参考已涵盖基础动效质量。
使用时: 用户询问动画、缓动、弹簧、过渡、交互感受或让它更流畅。
跳过时: 构建标准 UI,且设计评审中的动效参考已足够。
基于 Emil Kowalski 的Web 动画课程,一份关于创建感觉正确的动画的综合指南。
初始响应
当首次调用此技能且没有具体问题时,仅回复:
我已准备好根据 Emil Kowalski 的 animations.dev 课程帮助您处理动画。
在用户提出问题之前,不要提供任何其他信息。
审查格式(必需)
审查动画时,您必须使用 markdown 表格。不要使用带有之前:和之后:的列表形式。始终输出如下所示的实际 markdown 表格:
| 之前 | 之后 |
|---|
| transform: scale(0) | transform: scale(0.95) |
| animation: fadeIn 400ms ease-in |
animation: fadeIn 200ms ease-out |
| 无减少动效支持 | @media (prefers-reduced-motion: reduce) {...} |
错误格式(切勿这样做):
之前: transform: scale(0)
之后: transform: scale(0.95)
────────────────────────────
之前: 400ms 持续时间
之后: 200ms
正确格式:一个包含 | 之前 | 之后 | 列的 markdown 表格,每个问题一行。
决策树:我该使用什么工具?
这涉及 React 中的布局变化、共享过渡或退出动画吗?
├── 是 → Framer Motion(布局动画、AnimatePresence、layoutId)
│ 从 motion/react 导入(不是 framer-motion)
└── 否
├── 是简单的进入/退出或悬停? → CSS 过渡/关键帧
├── 对性能要求高(重页面、多元素)? → CSS(硬件加速)
├── 需要弹簧物理或可中断性? → Framer Motion
├── 需要手势跟踪(拖拽、光标跟随)? → FM 动效值
└── 是恒定速度循环? → CSS 关键帧
始终检查 prefers-reduced-motion。没有例外。
快速入门
每个动画决策都从这些问题开始:
- 1. 此元素是进入还是退出? → 使用 ease-out
- 屏幕上的元素在移动吗? → 使用 ease-in-out
- 这是悬停/颜色过渡吗? → 使用 ease
- 用户每天会看到这个 100 次以上吗? → 不要动画化
缓动蓝图
ease-out(最常见)
用于用户发起的交互:下拉菜单、模态框、工具提示、任何进入或退出屏幕的元素。
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(用于移动)
当屏幕上已有的元素需要移动或变形时使用。模仿自然运动,如汽车加速然后刹车。
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(用于悬停效果)
用于悬停状态和颜色过渡。不对称曲线(开始快,结束慢)对于温和的动画感觉优雅。
css
transition: background-color 150ms ease;
linear(在 UI 中避免)
仅用于:
- - 恒定速度动画(跑马灯、滚动条)
- 时间可视化(按住删除进度指示器)
线性对于交互元素感觉机械且不自然。
ease-in(几乎从不使用)
避免用于 UI 动画。 使界面感觉迟缓,因为缓慢的开始延迟了视觉反馈。
配对元素规则
一起动画的元素必须使用相同的缓动和持续时间。模态框 + 遮罩层、工具提示 + 箭头、抽屉 + 背景——如果它们作为一个单元移动,它们应该感觉像一个单元。
css
/ 两者使用相同的时序 /
.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 次以上的任何内容
- 当速度比流畅更重要时
营销 vs. 产品:
- - 营销:更精细,允许更长的持续时间
- 产品:快速、有目的、绝不轻浮
弹簧动画
弹簧感觉更自然,因为它们没有固定的持续时间——它们模拟真实的物理。
何时使用弹簧
- - 带动量的拖拽交互
- 应该感觉有生命的元素(灵动岛)
- 可以在动画中途中断的手势
- 有机、有趣的界面
配置
Apple 的方法(推荐):
js
// 持续时间 + 弹跳(更容易理解)
{ type: spring, duration: 0.5, bounce: 0.2 }
传统物理:
js
// 质量、刚度、阻尼(更复杂)
{ type: spring, mass: 1, stiffness: 100, damping: 10 }
弹跳指南
- - 在大多数 UI 上下文中避免弹跳
- 对拖拽关闭、有趣的交互使用弹跳
- 使用时保持弹跳微妙(0.1-0.3)
可中断性
弹簧在中断时保持速度——CSS 动画从零重新开始。这使得弹簧非常适合用户可能中途改变方向的手势。
布局动画(Framer Motion)
最强大的 FM 功能。允许动画化 CSS 无法做到的属性:flex-direction、justify-content、位置变化。
layout 属性
向任何 motion.* 元素添加 layout 以自动动画化布局变化:
jsx
当此元素的大小或位置发生变化时(由于状态、内容、兄弟元素),FM 平滑地动画化它。无需手动测量。
共享布局动画(layoutId