ui-animation
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUI Animation
UI动画
Tasteful UI animation with proper timing, accessibility, and performance.
通过合理的时长控制、无障碍设计和性能优化实现优雅的UI动画。
Quick Start
快速开始
Technique Decision:
- Simple transition? → CSS/Tailwind (,
transition-*)animate-* - Enter/exit with unmount? → Motion +
AnimatePresence - Gesture-driven? → Motion springs
- Layout changes? → Motion prop
layout
Default: Start with Easing Decision Tree → Duration Guidelines → Implement → Add a11y → Verify
技术选型决策:
- 简单过渡效果? → CSS/Tailwind (、
transition-*)animate-* - 带卸载的入场/退场动画? → Motion +
AnimatePresence - 手势驱动的动画? → Motion 弹簧动画
- 布局变化动画? → Motion 属性
layout
默认流程: 从缓动决策树开始 → 参考时长规范 → 实现效果 → 添加无障碍适配 → 效果验证
Core Principles
核心原则
- Natural Motion: Mimic physics. Avoid linear easing - nothing moves at constant speed.
- Purposeful: Every animation must add meaning. If you can't explain its benefit, remove it.
- Fast: UI animations under 300ms. Hover effects under 150ms. Over 500ms feels sluggish.
- Interruptible: Use springs for gesture-driven animations - they handle interruption gracefully.
- Accessible: Always respect . Non-negotiable.
prefers-reduced-motion
- 自然运动: 模拟物理规律,避免线性缓动——现实中没有物体会以恒定速度移动。
- 有目的性: 每个动画都必须具备实际意义,如果你无法解释它的价值,就删掉它。
- 快速响应: UI动画控制在300ms以内,悬停效果低于150ms,超过500ms会有滞后感。
- 可中断: 手势驱动的动画使用弹簧效果,能够优雅地处理中断场景。
- 无障碍: 始终遵循设置,这是硬性要求。
prefers-reduced-motion
Workflow
工作流程
Step 1: Classify Animation Type
步骤1:确定动画类型
| Type | Examples | Technique |
|---|---|---|
| Micro-interaction | Button press, toggle, checkbox | CSS/Tailwind |
| Enter/Exit | Modal, toast, dropdown | Motion + AnimatePresence |
| Layout change | Accordion, reorder, expand | Motion |
| Shared element | Tab indicator, card expand | Motion |
| Gesture | Drag, swipe, pull-to-refresh | Motion springs |
| 类型 | 示例 | 实现技术 |
|---|---|---|
| 微交互 | 按钮点击、开关切换、复选框 | CSS/Tailwind |
| 入场/退场 | 弹窗、提示框、下拉菜单 | Motion + AnimatePresence |
| 布局变化 | 手风琴组件、列表重排、元素展开 | Motion |
| 共享元素 | 标签栏指示器、卡片展开 | Motion |
| 手势交互 | 拖拽、滑动、下拉刷新 | Motion 弹簧动画 |
Step 2: Choose Timing
步骤2:选择动画时长
- Use Easing Decision Tree below to select curve
- Use Duration Guidelines to select timing
- For gestures, use Spring Animations config
- 使用下方的缓动决策树选择缓动曲线
- 参考时长规范确定动画时长
- 手势类动画使用弹簧动画配置
Step 3: Implement
步骤3:实现动画
- Check for copy-paste patterns
references/recipes.md - Apply timing from Step 2
- Wrap unmounting elements in
AnimatePresence
Use when starting from a design screenshot or mockup. Use to compare expected vs implemented UI.
ui_to_artifactui_diff_check- 查看获取可直接复制的代码模板
references/recipes.md - 应用步骤2确定的时长参数
- 用包裹需要卸载的元素
AnimatePresence
如果从设计截图或原型开始开发,可以使用。使用对比预期效果与实际实现的UI差异。
ui_to_artifactui_diff_checkStep 4: Accessibility (Required)
步骤4:无障碍适配(必填)
- Check with
prefers-reduced-motionoruseReducedMotion()motion-safe: - Simplify to opacity-only for reduced motion users
- Verify focus timing (move focus AFTER animation starts)
- 通过或
useReducedMotion()检测motion-safe:设置prefers-reduced-motion - 为减少动画偏好的用户简化为仅透明度变化的动画
- 验证焦点移动时机(在动画开始后再移动焦点)
Step 5: Verify
步骤5:效果验证
- Exit animations run (not instant unmount)
- elements have
opacity: 0pointerEvents: none - Focus moves after animation starts, not before
- Only animating and
transformopacity
- 退场动画正常执行(不会瞬间卸载)
- 的元素设置了
opacity: 0pointerEvents: none - 焦点在动画开始后移动,而非之前
- 仅对和
transform属性做动画opacity
Easing
缓动
Decision Tree
决策树
text
What triggers the animation?
│
├─ User action (click, tap, open)?
│ └─ Use: ease-out (fast start, slow end = responsive)
│
├─ Element moving on-screen (tab switch, reorder)?
│ └─ Use: ease-in-out (accelerate then decelerate)
│
├─ Continuous/looping (spinner, marquee)?
│ └─ Use: linear (constant speed appropriate here)
│
├─ Gesture-based (drag, swipe, pull)?
│ └─ Use: Spring animation (physics-based, interruptible)
│
└─ Hover/focus effect?
└─ Use: CSS ease, 150ms (subtle, immediate)text
动画的触发原因是什么?
│
├─ 用户操作(点击、轻触、打开)?
│ └─ 选用:ease-out(启动快、结束慢 = 响应感强)
│
├─ 元素在屏幕内移动(标签切换、重排)?
│ └─ 选用:ease-in-out(先加速后减速)
│
├─ 持续/循环动画(加载指示器、跑马灯)?
│ └─ 选用:linear(恒定速度适合该场景)
│
├─ 手势驱动(拖拽、滑动、拉动)?
│ └─ 选用:弹簧动画(基于物理效果、可中断)
│
└─ 悬停/焦点效果?
└─ 选用:CSS ease, 150ms(细腻、响应即时)Quick Reference
速查表
| Purpose | CSS | Tailwind | Duration |
|---|---|---|---|
| Modal/drawer enter | | | 200ms |
| Modal/drawer exit | | | 150ms |
| On-screen movement | | | 200-300ms |
| Hover effect | | | 150ms |
| Button press | — | | instant |
| 用途 | CSS | Tailwind | 时长 |
|---|---|---|---|
| 弹窗/抽屉入场 | | | 200ms |
| 弹窗/抽屉退场 | | | 150ms |
| 屏幕内元素移动 | | | 200-300ms |
| 悬停效果 | | | 150ms |
| 按钮点击 | — | | 即时 |
Pro Curves
专业缓动曲线
| Name | Value | Use Case |
|---|---|---|
| Vaul (buttery) | | Sheets, drawers, modals |
| Emphasized | | Material Design 3 |
| Snappy | | Fast UI transitions |
Avoid: Built-in —starts slow, feels sluggish.
ease-in| 名称 | 取值 | 适用场景 |
|---|---|---|
| Vaul(丝滑款) | | 底层面板、抽屉、弹窗 |
| 强调款 | | Material Design 3 |
| 灵动款 | | 快速UI过渡 |
避免使用: 内置的——启动慢,会有滞后感。
ease-inDuration Guidelines
时长规范
| Type | Duration | Notes |
|---|---|---|
| Micro-feedback | 100-150ms | Button press, toggle, checkbox |
| Small transition | 150-250ms | Tooltip, icon morph |
| Medium transition | 200-300ms | Modal, popover, dropdown |
| Large transition | 300-400ms | Page transition, complex layout |
| Maximum | <500ms | Exceptions: onboarding, data viz |
Key Rules:
- Exit faster than enter: 200ms enter → 150ms exit
- Hover = fast: Under 150ms
- High-frequency = instant: Keyboard nav, scrolling—<100ms or none
| 类型 | 时长 | 说明 |
|---|---|---|
| 微反馈 | 100-150ms | 按钮点击、开关切换、复选框 |
| 小型过渡 | 150-250ms | 提示框、图标变形 |
| 中型过渡 | 200-300ms | 弹窗、弹出层、下拉菜单 |
| 大型过渡 | 300-400ms | 页面转场、复杂布局变化 |
| 最大上限 | <500ms | 例外场景:新手引导、数据可视化 |
核心规则:
- 退场快于入场: 入场200ms → 退场150ms
- 悬停 = 快速: 低于150ms
- 高频操作 = 即时: 键盘导航、滚动——<100ms或无动画
Spring Animations
弹簧动画
Duration-based (Recommended)
基于时长的配置(推荐)
Easier to compose with other timed animations. Use (time to visually reach target) and (0 = no bounce, 1 = very bouncy).
visualDurationbounce| Feel | Config | Use Case |
|---|---|---|
| Snappy | | Tabs, buttons, quick feedback |
| Standard | | Modals, menus, general UI |
| Gentle | | Smooth, human-like flow |
更容易与其他定时动画组合。使用(视觉上到达目标位置的时间)和(0 = 无回弹,1 = 极强回弹)参数。
visualDurationbounce| 质感 | 配置 | 适用场景 |
|---|---|---|
| 灵动 | | 标签栏、按钮、快速反馈 |
| 标准 | | 弹窗、菜单、通用UI |
| 柔和 | | 流畅、拟人的过渡效果 |
Physics-based (Legacy/Advanced)
基于物理参数的配置(旧版/高级用法)
Use when integrating with physics libraries or when precise control over spring dynamics is needed.
| Feel | Config | Use Case |
|---|---|---|
| Snappy | | High-frequency interactions |
| Standard | | Framer Handshake convention |
| Gentle | | react-motion preset |
Gotcha:/stiffness/dampingoverridesmass/duration. Pick one approach—don't mix.bounce
适合与物理引擎库集成,或需要精确控制弹簧动态效果的场景。
| 质感 | 配置 | 适用场景 |
|---|---|---|
| 灵动 | | 高频交互场景 |
| 标准 | | Framer 默认约定 |
| 柔和 | | react-motion 预设 |
注意:/stiffness/damping参数会覆盖mass/duration,选择一种方案即可,不要混用。bounce
Layout Animations
布局动画
The layout
Prop
layoutlayout
属性
layoutAdd to animate position/size changes automatically. Use for text (prevents distortion).
layoutlayout="position"| Prop Value | Effect | Use Case |
|---|---|---|
| Animates position AND size | Default for flexible elements |
| Animates only translation | Text/icons that shouldn't stretch |
| Animates only dimensions | Fixed-position expanding panels |
添加属性即可自动为位置/尺寸变化添加动画。对文本使用避免变形。
layoutlayout="position"| 属性值 | 效果 | 适用场景 |
|---|---|---|
| 同时为位置和尺寸添加动画 | 弹性元素的默认选项 |
| 仅为位移添加动画 | 不希望拉伸的文本/图标 |
| 仅为尺寸变化添加动画 | 固定位置的展开面板 |
Shared Element Transitions (layoutId
)
layoutId共享元素过渡(layoutId
)
layoutIdElements with matching animate between each other when entering/exiting.
layoutIdCritical Trap: Duplicate values cause elements to teleport across the page. Use unique IDs per context or wrap in .
layoutId<LayoutGroup id="...">拥有相同的元素在入场/退场时会自动生成过渡动画。
layoutId重要陷阱: 重复的会导致元素在页面上“瞬移”,每个上下文内使用唯一ID,或者用包裹。
layoutId<LayoutGroup id="...">Layout Gotchas
布局动画注意事项
- Text distortion: Apply to text elements
layout="position" - Border radius: Can warp during scale—Motion auto-corrects, but test it
- SVG elements: doesn't work on
layout—use manual morphing<path>
- 文本变形: 为文本元素添加
layout="position" - 圆角: 缩放过程中可能变形,Motion会自动修正,但仍需测试
- SVG元素: 对
layout无效,使用手动变形实现动画<path>
Gesture Gotchas
手势交互注意事项
| Problem | Solution |
|---|---|
| Touch scroll conflicts | |
| Element snaps back | Check |
| Momentum feels wrong | |
| One-direction only | |
Swipe dismiss: Check BOTH distance AND velocity—users expect flicks to work.
| 问题 | 解决方案 |
|---|---|
| 触摸滚动冲突 | |
| 元素回弹 | 检查 |
| 滑动动量不符合预期 | 精准控制的UI可设置 |
| 仅允许单方向拖拽 | |
滑动关闭: 同时检测滑动距离和速度——用户希望轻扫即可触发关闭。
Accessibility
无障碍
prefers-reduced-motion (REQUIRED)
prefers-reduced-motion(必填)
tsx
import { useReducedMotion } from "motion/react"
const shouldReduce = useReducedMotion()
const variants = shouldReduce
? { opacity: 1 } // Fade only
: { opacity: 1, scale: 1, y: 0 } // Full animationTailwind: /
motion-safe:animate-pulsemotion-reduce:transition-noneBest practice: Don't disable—simplify. Remove spatial movement, keep opacity.
tsx
import { useReducedMotion } from "motion/react"
const shouldReduce = useReducedMotion()
const variants = shouldReduce
? { opacity: 1 } // 仅淡入淡出
: { opacity: 1, scale: 1, y: 0 } // 完整动画Tailwind用法: /
motion-safe:animate-pulsemotion-reduce:transition-none最佳实践: 不要完全禁用动画,而是简化效果。移除空间移动效果,保留透明度变化即可。
Focus Management
焦点管理
- Move focus AFTER animation starts:
requestAnimationFrame(() => ref.focus()) - Restore focus to trigger on close
- Don't animate inside regions
aria-live
- 在动画开始后再移动焦点:
requestAnimationFrame(() => ref.focus()) - 关闭时将焦点恢复到触发元素
- 不要在区域内添加动画
aria-live
Touch Targets
触摸目标尺寸
| Standard | Size | Tailwind | Physical |
|---|---|---|---|
| Material Design | 48×48 dp | | ~9mm (recommended) |
| Apple HIG | 44×44 pt | | ~7mm |
| WCAG 2.2 (AA) | 24×24 px | | ~5mm (minimum) |
Why? Average adult finger pad is ~9mm. Targets below 7mm cause "fat finger" errors. Use Material's 48dp for cross-platform; Apple's 44pt is iOS-specific minimum.
| 规范 | 尺寸 | Tailwind类 | 物理尺寸 |
|---|---|---|---|
| Material Design | 48×48 dp | | ~9mm(推荐) |
| Apple HIG | 44×44 pt | | ~7mm |
| WCAG 2.2 (AA) | 24×24 px | | ~5mm(最低要求) |
原因: 成年人平均指垫宽度约9mm,小于7mm的目标会导致“胖手指”误触。跨平台场景使用Material的48dp标准;Apple的44pt是iOS专属最低要求。
Performance
性能优化
Golden Rules
黄金准则
- Only animate and
transform—GPU-acceleratedopacity - Never animate: ,
width,height,top,left,marginpadding - sparingly—only during animation, remove after
will-change - Blur thresholds:
- ≤10px: Safe for animation
- 11-20px: May cause jank on mobile/4K—test thoroughly
-
20px: Avoid for real-time effects; use pre-blurred images instead
- Prefer CSS over JS for simple transitions
- 仅对和
transform做动画——GPU加速属性opacity - 绝对不要对这些属性做动画: 、
width、height、top、left、marginpadding - 谨慎使用——仅在动画过程中添加,动画结束后移除
will-change - 模糊阈值:
- ≤10px: 动画安全
- 11-20px: 移动端/4K屏幕可能出现卡顿,需充分测试
-
20px: 避免实时动画效果,改用预模糊的图片
- 简单过渡优先使用CSS而非JS实现
Key Traps
常见陷阱
- Height animation: Use prop, not
layoutanimate={{ height }} - Invisible but clickable: still receives clicks—add
opacity: 0pointerEvents: "none" - will-change everywhere: Causes layer explosion, mobile crashes
See for detailed examples.
references/recipes.md- 高度动画: 使用属性,不要用
layoutanimate={{ height }} - 不可见但可点击: 的元素仍会响应点击,添加
opacity: 0pointerEvents: "none" - 到处使用will-change: 会导致图层爆炸,移动端崩溃
查看获取详细示例。
references/recipes.mdExamples
示例
Copy-paste patterns organized by category in :
references/recipes.md- Common UI Patterns: Button press, modal enter/exit, error shake, staggered lists, accordion
- Touch & Interaction: Accessible touch targets, hover on touch devices, instant tooltips
- Layout Animations: prop,
layoutshared elements, collision fixeslayoutId - Radix UI Integration: pattern,
forceMount, origin-aware popoversasChild - Accessibility: Focus timing, focus restoration, reduced motion variants
- Performance: Height animation (use ), invisible-but-clickable fix,
layoutwill-change - Exit Patterns: with
popLayout, SSR hydration (forwardRef)initial={false} - Gestures: Swipe dismiss with velocity check, elastic drag boundaries
references/recipes.md- 通用UI模式: 按钮点击、弹窗入场/退场、错误抖动、 staggered列表、手风琴
- 触摸与交互: 无障碍触摸目标、触摸设备悬停效果、即时提示框
- 布局动画: 属性、
layout共享元素、碰撞修复layoutId - Radix UI集成: 模式、
forceMount、基于原点的弹出层asChild - 无障碍: 焦点时机控制、焦点恢复、减少动画适配变体
- 性能优化: 高度动画(用实现)、不可见元素点击修复、
layout用法will-change - 退场模式: 结合使用
forwardRef、SSR hydration(popLayout)initial={false} - 手势交互: 带速度检测的滑动关闭、弹性拖拽边界
AnimatePresence
AnimatePresence
| Mode | Behavior | Use Case |
|---|---|---|
| Simultaneous enter/exit | Crossfades, overlays |
| Exit completes before enter | Page transitions, tabs |
| Exiting elements leave flow | List removals (with |
| 模式 | 行为 | 适用场景 |
|---|---|---|
| 入场退场同时执行 | 交叉淡入、覆盖层 |
| 退场完成后再执行入场 | 页面转场、标签切换 |
| 退场元素脱离文档流 | 列表删除(配合 |
Exit Animation Trap
退场动画陷阱
Exit animations require —without it, unmount is instant:
AnimatePresencetsx
// ❌ Exit never runs
{isOpen && <motion.div exit={{ opacity: 0 }}>...</motion.div>}
// ✅ Wrap in AnimatePresence
<AnimatePresence>
{isOpen && <motion.div exit={{ opacity: 0 }}>...</motion.div>}
</AnimatePresence>SSR: Use to prevent animation on page load.
<AnimatePresence initial={false}>退场动画必须配合使用,否则元素会瞬间卸载:
AnimatePresencetsx
// ❌ 退场动画不会执行
{isOpen && <motion.div exit={{ opacity: 0 }}>...</motion.div>}
// ✅ 用AnimatePresence包裹
<AnimatePresence>
{isOpen && <motion.div exit={{ opacity: 0 }}>...</motion.div>}
</AnimatePresence>SSR场景: 使用避免页面加载时触发动画。
<AnimatePresence initial={false}>Anti-patterns
反模式
| Don't | Do Instead | Why |
|---|---|---|
| | Avoids "popping" effect |
| | Linear feels robotic |
| Animations >500ms | Keep under 300ms | Feels sluggish |
| Same tooltip delay | First: 400ms, subsequent: 0ms | User mental model |
| Skip reduced-motion | Always | Accessibility |
| Animate layout props | Use | Performance |
| Excessive bounce | | Unprofessional |
| 错误做法 | 替代方案 | 原因 |
|---|---|---|
初始状态 | | 避免“弹出”的突兀感 |
UI动画用 | 用 | 线性效果太机械 |
| 动画时长>500ms | 保持在300ms以内 | 会有滞后感 |
| 所有提示框使用相同延迟 | 首次: 400ms, 后续: 0ms | 符合用户心理预期 |
| 忽略减少动画适配 | 始终使用 | 无障碍要求 |
| 对布局属性做动画 | 使用 | 性能更好 |
| 过多回弹效果 | | 不够专业 |
tailwindcss-animate
tailwindcss-animate
Tailwind v4: Define keyframes viain CSS, not config.@theme
| Category | Classes |
|---|---|
| Enter | |
| Exit | |
| Timing | |
| Fill Mode | |
Tailwind v4: 通过CSS中的定义关键帧,不要在配置文件中定义。@theme
| 分类 | 类名 |
|---|---|
| 入场 | |
| 退场 | |
| 时序 | |
| 填充模式 | |
Integration with Other Skills
与其他技能的集成
| When | Skill | Why |
|---|---|---|
| After implementing | | Ensure code passes checks |
| Reusable patterns | | Document component API |
| Before committing | | Use |
| Integration issues | | Look up latest patterns |
| 场景 | 技能 | 原因 |
|---|---|---|
| 实现完成后 | | 确保代码通过检查 |
| 可复用模式 | | 编写组件API文档 |
| 提交代码前 | | 使用 |
| 集成问题 | | 查找最新的实现模式 |
Output
输出
- Artifacts: Code changes only (no outputs)
.ada/ - Modifications: Component animations, CSS/Tailwind styles, Motion configs
- Type: Workflow skill (guidance only, no scripts)
- 产物: 仅代码变更(无输出)
.ada/ - 修改内容: 组件动画、CSS/Tailwind样式、Motion配置
- 类型: 工作流技能(仅提供指导,无脚本)
References
参考资料
Internal
内部
- - Copy-paste patterns, integration examples, detailed traps
references/recipes.md
- - 可复制的代码模板、集成示例、常见陷阱详解
references/recipes.md
External
外部
- Motion Documentation
- Material Design 3 Motion
- Apple HIG - Motion
- tailwindcss-animate
- easings.net - Easing function cheat sheet