CSS 动画与过渡:完整指南及示例
CSS 动画和过渡让 UI 生动起来。做得好,它们能引导注意力并提升感知性能;做得差,则会分散注意力并拖慢应用。以下是如何正确使用它们。
CSS 过渡:简单的状态变化
过渡在状态变化(悬停、聚焦、激活、类名改变)之间对属性变化进行动画处理。
/* 语法:transition: property duration timing-function delay */
.button {
background: #3b82f6;
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
/* 对特定属性进行动画 */
transition: background 200ms ease,
transform 150ms ease,
box-shadow 200ms ease;
}
.button:hover {
background: #2563eb;
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
}
/* 简写:transition: all */
.button { transition: all 200ms ease; }
/* ⚠️ 避免在生产中使用 transition: all —— 影响性能 */

时间函数
.element {
transition-timing-function: ease; /* 默认:慢-快-慢 */
transition-timing-function: linear; /* 匀速 */
transition-timing-function: ease-in; /* 开始慢 */
transition-timing-function: ease-out; /* 结束慢 ← 用于进入 UI 元素 */
transition-timing-function: ease-in-out; /* 两端慢 */
/* 自定义 cubic-bezier */
transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); /* 弹簧弹跳 */
/* 类似弹簧 */
transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
合适的过渡时长
小型 UI 反馈(悬停、激活): 100-150ms
颜色/透明度变化: 200-300ms
面板、抽屉滑入: 200-350ms
页面过渡: 300-500ms
装饰/主视觉动画: 500ms-1s
规则:响应用户输入的过渡应感觉即时(< 200ms)
装饰性动画可以更慢
CSS 动画:@keyframes
动画自动运行,可以循环,并且有更多控制点。
/* 定义动画 */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* 或使用百分比表示多个步骤 */
@keyframes slideInUp {
0% { transform: translateY(20px); opacity: 0; }
100% { transform: translateY(0); opacity: 1; }
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
/* 应用动画 */
.card {
animation-name: fadeIn;
animation-duration: 300ms;
animation-timing-function: ease-out;
animation-delay: 0ms;
animation-iteration-count: 1; /* 或:infinite, 3 */
animation-direction: normal; /* 或:reverse, alternate */
animation-fill-mode: both; /* 保持最终状态 */
animation-play-state: running; /* 或:paused */
/* 简写 */
animation: fadeIn 300ms ease-out both;
/* 多个动画 */
animation: fadeIn 300ms ease-out, pulse 2s ease-in-out infinite 300ms;
}
animation-fill-mode(重要!)
/* none:动画结束后元素返回原始状态 */
.element { animation: fadeIn 500ms none; }
/* forwards:保持最终关键帧状态 */
.element { animation: fadeIn 500ms forwards; }
/* backwards:在延迟期间应用第一个关键帧 */
.element { animation: fadeIn 500ms 200ms backwards; }
/* both:同时应用 backwards 和 forwards */
.element { animation: fadeIn 500ms 200ms both; } /* ← 通常是你想要的 */

实用动画模式
加载骨架屏
@keyframes shimmer {
0% { background-position: -200% center; }
100% { background-position: 200% center; }
}
.skeleton {
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 4px;
}
.skeleton-text { height: 1em; width: 80%; margin-bottom: 8px; }
.skeleton-title { height: 1.5em; width: 60%; }
旋转加载
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner {
width: 24px;
height: 24px;
border: 3px solid rgba(59, 130, 246, 0.2);
border-top-color: #3b82f6;
border-radius: 50%;
animation: spin 600ms linear infinite;
}
通知提示滑入
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOutRight {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(110%);
opacity: 0;
}
}
.toast {
animation: slideInRight 250ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
.toast.dismissing {
animation: slideOutRight 200ms ease-in both;
}

交错列表动画
@keyframes fadeSlideIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.list-item {
animation: fadeSlideIn 300ms ease-out both;
}
/* 使用 nth-child 交错 */
.list-item:nth-child(1) { animation-delay: 0ms; }
.list-item:nth-child(2) { animation-delay: 50ms; }
.list-item:nth-child(3) { animation-delay: 100ms; }
.list-item:nth-child(4) { animation-delay: 150ms; }
/* 更好:通过 JS 设置 CSS 自定义属性 */
.list-item {
animation-delay: calc(var(--index, 0) * 50ms);
}
// 在每个元素上设置 --index
document.querySelectorAll('.list-item').forEach((item, i) => {
item.style.setProperty('--index', i);
});
性能:关键规则
只对 transform 和 opacity 进行动画。 这些在 GPU 合成器线程上运行,不会触发布局。
/* ✅ GPU 加速:流畅 60fps */
.element {
transform: translateX(100px); /* 移动 */
transform: scale(1.1); /* 缩放 */
transform: rotate(45deg); /* 旋转 */
opacity: 0.5; /* 淡入淡出 */
}
/* ❌ 触发布局(昂贵):导致卡顿 */
.element {
width: 200px; /* 布局 */
height: 100px; /* 布局 */
top: 20px; /* 布局 */
left: 10px; /* 布局 */
margin: 10px; /* 布局 */
}
/* ❌ 触发绘制(中等开销) */
.element {
background-color: red; /* 绘制 */
color: blue; /* 绘制 */
box-shadow: ...; /* 绘制 */
}
will-change
/* 提示浏览器创建新的合成器层 */
.animated-element {
will-change: transform, opacity;
}
/* ⚠️ 谨慎使用 —— 每个层都会消耗 GPU 内存 */
/* 在动画开始前添加,结束后移除 */
element.addEventListener('mouseenter', () => {
element.style.willChange = 'transform';
});
element.addEventListener('transitionend', () => {
element.style.willChange = 'auto';
});
无障碍:尊重用户偏好
/* 尊重减少动画偏好 */
@media (prefers-reduced-motion: reduce) {
/* 为偏好减少动画的用户移除动画 */
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* 或提供替代的简化动画 */
@media (prefers-reduced-motion: no-preference) {
.hero-animation {
animation: fadeSlideIn 600ms ease-out both;
}
}
CSS 动画 vs JavaScript 动画
| 特性 | CSS | JS(GSAP 等) |
|---|---|---|
| 性能 | ✅ 合成器友好 | ✅ 使用得当也友好 |
| 控制 | 有限 | 完全控制 |
| 排序 | 困难 | 容易 |
| 物理 | 无 | 有(弹簧) |
| 交互性 | 有限 | 完全 |
| SVG 路径 | 有限 | 支持 |
| 最佳用途 | 简单的状态变化 | 复杂的时间线 |
对于大多数 UI 过渡 → CSS。对于复杂的序列/滚动动画 → GSAP 或 Web Animations API。
→ 使用 Color Converter 探索动画的配色方案。