关于 ui-animation
面向 UI 设计与开发者的动画设计指南,涵盖动效原则、实现方式、性能和无障碍考量,帮助规划有明确用途的界面动画。
主要功能
- 01动效原则与时长建议
- 02缓动函数与动画模式
- 03CSS 和 JavaScript 实现参考
- 04性能优化与 FLIP 技术
- 05减少动态效果与无障碍建议
适用场景
- 01规划按钮、切换控件等微交互
- 02设计加载状态与页面转场
- 03检查动画性能及减少动态效果支持
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ travisjneuman/ .claude --skill ui-animation安装后在 Claude Code 中输入/ui-animation确认已加载。
详细介绍
UI 动画与动效设计
全面指南:在用户界面中创建有目的且高性能的动画。
动画原则
动画的 12 条原则(应用于 UI)
| 原则 | UI 应用 |
|---|---|
| Timing(时机) | 时长体现重要性/距离 |
| Easing(缓动) | 自然加速/减速 |
| Anticipation(预备) | 为动作做好视觉准备 |
| Follow-through(跟随) | 停止后动量仍在延续 |
| Secondary Action(次要动作) | 辅助元素做出响应 |
| Staging(演出布局) | 吸引注意力,突出关键元素 |
| Squash & Stretch(挤压与拉伸) | 富有弹性、充满趣味的交互 |
| Exaggeration(夸张) | 强调重要反馈 |
| Arc(弧线) | 自然的弧形运动路径 |
| Overlap(重叠) | 元素以不同速度移动 |
| Solid Drawing(立体感) | 保持一致的 3D 空间 |
| Appeal(吸引力) | 引人入胜、令人愉悦的动效 |
为什么要使用动画?
FUNCTIONAL PURPOSES:
✓ Guide attention to important changes
✓ Show relationships between elements
✓ Provide feedback for actions
✓ Communicate system status
✓ Ease cognitive load
✓ Create spatial orientation
NOT FOR:
✗ Pure decoration
✗ Showing off skills
✗ Making things "feel modern"
✗ Distracting from content
时机与时长
时长指南
INSTANT (0-100ms):
└─→ Button state changes
└─→ Toggle switches
└─→ Micro-feedback
FAST (100-200ms):
└─→ Hover effects
└─→ Simple fades
└─→ Small movements
STANDARD (200-300ms):
└─→ Most UI transitions
└─→ Modal open/close
└─→ Dropdown menus
SLOW (300-500ms):
└─→ Complex transitions
└─→ Page transitions
└─→ Large element movement
DELIBERATE (500ms+):
└─→ Hero animations
└─→ Skeleton loading
└─→ Onboarding sequences
基于距离的时机
Rule: Longer distance = Longer duration
Small (< 100px): 150-200ms
Medium (100-300px): 200-300ms
Large (300-500px): 300-400ms
Full screen: 400-500ms
Formula:
duration = baseTime + (distance × factor)
缓动函数
标准缓动
LINEAR
├────────────────────────────┤
Constant speed. Rarely natural.
Use: Progress bars, clock hands
EASE-OUT (Deceleration)
├═══════════────────────────┤
Fast start, slow end.
Use: Elements entering the screen
EASE-IN (Acceleration)
├────────────────═══════════┤
Slow start, fast end.
Use: Elements leaving the screen
EASE-IN-OUT (S-curve)
├────═══════════════────────┤
Slow start and end, fast middle.
Use: On-screen transitions
EASE-OUT-BACK (Overshoot)
├═══════════────────────╗───┤
Overshoots, settles back.
Use: Playful entrances, bounces
CSS 缓动值
/* Built-in keywords */
linear: cubic-bezier(0, 0, 1, 1)
ease: cubic-bezier(0.25, 0.1, 0.25, 1)
ease-in: cubic-bezier(0.42, 0, 1, 1)
ease-out: cubic-bezier(0, 0, 0.58, 1)
ease-in-out: cubic-bezier(0.42, 0, 0.58, 1)
/* Material Design standard */
standard: cubic-bezier(0.4, 0, 0.2, 1)
decelerate: cubic-bezier(0, 0, 0.2, 1)
accelerate: cubic-bezier(0.4, 0, 1, 1)
/* Custom: Snappy */
snappy: cubic-bezier(0.5, 0, 0, 1)
/* Custom: Bouncy */
bouncy: cubic-bezier(0.68, -0.55, 0.27, 1.55)
/* Spring-like (use JS libraries) */
spring: { stiffness: 300, damping: 20 }
何时使用各类缓动
| 场景 | 缓动 | 原因 |
|---|---|---|
| 元素进入 | ease-out | 有力地进入,然后平稳停下 |
| 元素离开 | ease-in | 积蓄动量后离开 |
| 屏幕内变化 | ease-in-out | 状态变化平滑 |
| 吸引注意力 | bounce/spring | 俏皮、醒目 |
| 背景/细微动效 | ease-out | 不显突兀 |
动画模式
微交互
BUTTON STATES:
┌─────────────────────────────────────────┐
│ Rest → Hover: scale(1.02), 100ms │
│ Hover → Active: scale(0.98), 50ms │
│ Active → Rest: scale(1), 150ms ease-out │
└─────────────────────────────────────────┘
TOGGLE SWITCH:
┌─────────────────────────────────────────┐
│ Thumb: translateX, 200ms ease-out │
│ Track: background-color, 200ms │
│ State: slight bounce at end │
└─────────────────────────────────────────┘
CHECKBOX:
┌─────────────────────────────────────────┐
│ Check mark: stroke-dashoffset animation │
│ Background: scale from center, 150ms │
│ Ripple: expanding circle, 300ms │
└─────────────────────────────────────────┘
加载状态
SKELETON SCREENS:
┌──────────────────────────┐
│ ▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░ │ Shimmer effect
│ ▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░ │ Linear gradient
│ ▓▓▓▓▓▓░░░░░░░░░░░░░░░░ │ Moving left to right
└──────────────────────────┘
CSS:
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
SPINNER:
- Duration: 1-2 seconds per rotation
- Easing: linear (consistent motion)
- Style: Match brand identity
页面过渡
CROSSFADE:
├──────────────────────────────────────────┤
│ Old page: opacity 1 → 0, 200ms │
│ New page: opacity 0 → 1, 200ms │
│ Timing: Sequential or overlapping │
└──────────────────────────────────────────┘
SLIDE:
├──────────────────────────────────────────┤
│ Direction follows navigation hierarchy │
│ Forward: Slide left (new from right) │
│ Back: Slide right (prev from left) │
│ Duration: 300-400ms │
└──────────────────────────────────────────┘
SHARED ELEMENT:
├──────────────────────────────────────────┤
│ Element morphs between states │
│ Position, size, border-radius change │
│ Creates continuity between screens │
│ Duration: 300-500ms │
└──────────────────────────────────────────┘
列表动画
STAGGERED ENTRANCE:
┌─ Item 1 ────────────────┐ delay: 0ms
├─ Item 2 ────────────────┤ delay: 50ms
├─ Item 3 ────────────────┤ delay: 100ms
├─ Item 4 ────────────────┤ delay: 150ms
└─ Item 5 ────────────────┘ delay: 200ms
Max total duration: 500ms
Stagger: 30-50ms per item
Animation: translateY + opacity
REORDER:
- Use FLIP technique
- Duration: 200-300ms
- Ease: ease-out
性能
GPU 加速属性
FAST (Compositor only):
✓ transform: translate, scale, rotate
✓ opacity
✓ filter (with will-change)
SLOW (Triggers layout/paint):
✗ width, height
✗ margin, padding
✗ top, left, right, bottom
✗ border, border-radius
✗ font-size
✗ box-shadow (repaints)
OPTIMIZATION:
will-change: transform, opacity;
/* Use sparingly! */
性能指南
/* Good: GPU-accelerated */
.animated-element {
transform: translateX(0);
transition: transform 300ms ease-out;
}
.animated-element.moved {
transform: translateX(100px);
}
/* Bad: Layout thrashing */
.animated-element {
left: 0;
transition: left 300ms ease-out;
}
.animated-element.moved {
left: 100px;
}
FLIP 技术
// First: Get initial position
const first = element.getBoundingClientRect();
// Last: Apply change, get final position
element.classList.add("moved");
const last = element.getBoundingClientRect();
// Invert: Calculate delta, apply inverse transform
const deltaX = first.left - last.left;
const deltaY = first.top - last.top;
element.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
// Play: Remove transform with transition
requestAnimationFrame(() => {
element.style.transition = "transform 300ms ease-out";
element.style.transform = "";
});
CSS 动画技术
关键帧动画
@keyframes fadeSlideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.element {
animation: fadeSlideIn 300ms ease-out forwards;
}
/* With steps */
@keyframes typewriter {
from {
width: 0;
}
to {
width: 100%;
}
}
.typing-text {
animation: typewriter 2s steps(20) forwards;
}
过渡
.button {
background: var(--primary);
transform: scale(1);
transition:
background 150ms ease-out,
transform 100ms ease-out;
}
.button:hover {
background: var(--primary-hover);
transform: scale(1.02);
}
.button:active {
transform: scale(0.98);
transition-duration: 50ms;
}
animation 简写属性
/* animation: name duration timing-function delay
iteration-count direction fill-mode play-state */
animation: slideIn 300ms ease-out 100ms 1 normal forwards running;
/* Common patterns */
animation: spin 1s linear infinite;
animation: pulse 2s ease-in-out infinite alternate;
animation: fadeIn 300ms ease-out forwards;
JavaScript 动画库
对比
| 库 | 最适合 | 包体积 |
|---|---|---|
| CSS | 简单过渡 | 0kb |
| Web Animations API | 原生、高性能 | 0kb |
| GSAP | 复杂、精准的动画 | ~60kb |
| Framer Motion | React 生态 | ~50kb |
| anime.js | 时间轴、SVG | ~17kb |
| Motion One | 现代、轻量 | ~18kb |
| Lottie | After Effects 导出 | ~50kb |
Framer Motion(React)
import { motion, AnimatePresence } from "framer-motion";
// Basic animation
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3, ease: "easeOut" }}
>
Content
</motion.div>;
// Variants for complex animations
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
};
<motion.ul variants={containerVariants} initial="hidden" animate="visible">
{items.map((item) => (
<motion.li key={item.id} variants={itemVariants}>
{item.name}
</motion.li>
))}
</motion.ul>;
GSAP
import { gsap } from "gsap";
// Basic tween
gsap.to(".element", {
x: 100,
opacity: 1,
duration: 0.3,
ease: "power2.out",
});
// Timeline
const tl = gsap.timeline();
tl.from(".header", { y: -100, duration: 0.5 })
.from(".content", { opacity: 0, duration: 0.3 }, "-=0.2")
.from(".button", { scale: 0.8, duration: 0.2 });
// ScrollTrigger
gsap.registerPlugin(ScrollTrigger);
gsap.to(".parallax", {
y: -100,
scrollTrigger: {
trigger: ".section",
start: "top center",
end: "bottom center",
scrub: true,
},
});
无障碍访问
尊重用户偏好
/* Reduce motion for users who prefer it */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* Or provide simpler alternatives */
@media (prefers-reduced-motion: reduce) {
.animated-element {
/* Replace motion with opacity */
animation: none;
transition: opacity 200ms ease;
}
}
安全的动画实践
AVOID for vestibular disorders:
✗ Parallax scrolling effects
✗ Zooming/scaling animations
✗ Spinning/rotating elements
✗ Auto-playing animations
✗ Flashing (>3 times/second)
SAFE alternatives:
✓ Opacity fades
✓ Color transitions
✓ Subtle position changes
✓ User-initiated animations
WCAG 指南
2.2.2 Pause, Stop, Hide:
- Auto-updating content can be paused
- No time limits on reading
2.3.1 Three Flashes:
- Nothing flashes more than 3x per second
2.3.3 Animation from Interactions:
- Motion can be disabled
- Triggered animations respect preferences
移动端注意事项
触控反馈
/* iOS-style tap feedback */
.button {
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
}
.button:active {
opacity: 0.7;
transition: opacity 50ms;
}
/* Ripple effect */
.ripple {
position: relative;
overflow: hidden;
}
.ripple::after {
content: "";
position: absolute;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
transform: scale(0);
animation: ripple 400ms ease-out;
}
平台惯例
| 平台 | 动画风格 |
|---|---|
| iOS | 弹簧物理效果,300-500ms,细腻 |
| Android | Material 动效,200-300ms,强调感更强 |
| Web | 各不相同,通常为 200-400ms |
设计系统集成
动画令牌
:root {
/* Durations */
--duration-instant: 100ms;
--duration-fast: 150ms;
--duration-normal: 250ms;
--duration-slow: 400ms;
--duration-slower: 600ms;
/* Easings */
--ease-default: cubic-bezier(0.4, 0, 0.2, 1);
--ease-in: cubic-bezier(0.4, 0, 1, 1);
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-bounce: cubic-bezier(0.68, -0.55, 0.27, 1.55);
/* Combined */
--transition-default: var(--duration-normal) var(--ease-default);
--transition-fast: var(--duration-fast) var(--ease-out);
}
/* Usage */
.element {
transition: transform var(--transition-default);
}
最佳实践
应该:
- 有目的地使用动画(反馈、引导)
- 使用 GPU 加速属性
- 将时长控制在 500ms 以内
- 让动画契合品牌个性
- 尊重 prefers-reduced-motion
- 在低端设备上测试
- 使用一致的时机系统
不要:
- 仅为装饰而添加动画
- 阻止用户交互
- 过度使用弹跳或过冲效果
- 引发晕动症
- 忘记退出动画
- 忽视性能指标
- 延迟呈现重要内容
动画检查清单
实施前
- 动画有明确用途
- 时长与操作相匹配
- 缓动效果符合运动意图
- 已评估对性能的影响
实施
- 使用 GPU 加速的属性
- 遵循 prefers-reduced-motion 设置
- 在目标设备上运行正常
- 不会造成布局抖动
质量检查
- 感觉自然且响应灵敏
- 不会让用户等待
- 与设计系统保持一致
- 所有用户都能访问
评论
支持 Markdown