一句话概括
CSS 动画体系由 transition(过渡) 和 animation(动画) 两大核心组成:transition 负责”从 A 到 B 的状态变化”,animation + @keyframes 负责”多阶段、可循环的复杂运动”,两者底层都依赖浏览器的合成器线程来实现高性能渲染。
背景与意义
为什么需要 CSS 动画?
在早期的 Web 开发中,实现动画效果主要依赖 JavaScript + setInterval 或 requestAnimationFrame。这种方式虽然灵活,但存在三个核心问题:
- 性能瓶颈:JS 动画运行在主线程,与布局、绘制、JS 执行竞争 CPU 时间片
- 代码复杂:一个简单的渐入效果需要几十行 JS 代码
- 维护困难:多段动画的时间协调、暂停、反转都需要手动管理
CSS 动画的诞生改变了这一切:
1
2
3
4
5
6
7
/* 4 行 CSS 代替 30 行 JS */
.element {
transition: opacity 0.3s ease;
}
.element:hover {
opacity: 0.5;
}
更重要的是:CSS 动画可以运行在合成器线程,即使主线程被 JS 阻塞,动画依然流畅。
面试高频信号
CSS 动画面试题在 2025—2026 年的出现频率持续攀升,尤其是:
- transition 与 animation 的区别(送分题,但常有人答不全)
will-change和硬件加速的原理(进阶题)- 动画性能优化(考察”懂渲染流程”的深度)
概念与定义
transition:状态之间的过渡
语法:
1
2
3
4
5
6
7
8
9
10
/* 简写 */
transition: property duration timing-function delay;
/* 实际使用 */
.box {
transition: all 0.3s ease; /* 所有属性变化都过渡 */
transition: opacity 0.3s ease; /* 只过渡 opacity */
transition: transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1),
opacity 0.5s ease 0.1s; /* 多个属性独立过渡 */
}
触发条件: transition 需要状态变化来触发——hover、类名切换、JS 修改 style 等。
1
2
3
4
5
6
7
8
9
.btn {
background: blue;
transform: scale(1);
transition: transform 0.2s, background 0.3s;
}
.btn:hover {
background: darkblue;
transform: scale(1.05);
}
animation:独立的动画序列
语法:
1
2
3
4
5
6
7
8
@keyframes slideIn {
from { transform: translateX(-100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.element {
animation: slideIn 0.5s ease-out forwards;
}
animation 子属性一览:
1
2
3
4
5
6
7
8
9
10
.element {
animation-name: slideIn; /* @keyframes 名称 */
animation-duration: 0.5s; /* 持续时间 */
animation-timing-function: ease-out; /* 缓动函数 */
animation-delay: 0s; /* 延迟 */
animation-iteration-count: 1; /* 播放次数,infinite = 无限 */
animation-direction: normal; /* normal | reverse | alternate | alternate-reverse */
animation-fill-mode: forwards; /* none | forwards | backwards | both */
animation-play-state: running; /* running | paused */
}
最小示例
1
<div class="card">Hover me</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* transition 示例 */
.card {
width: 200px;
height: 100px;
background: #42b883;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
box-shadow 0.3s ease,
background 0.3s ease;
}
.card:hover {
transform: scale(1.1) translateY(-4px);
box-shadow: 0 8px 24px rgba(66, 184, 131, 0.4);
background: #3aa876;
}
1
2
3
4
5
6
7
8
9
10
/* animation 示例:呼吸光效 */
@keyframes breathe {
0% { box-shadow: 0 0 0 0 rgba(66, 184, 131, 0.6); }
50% { box-shadow: 0 0 0 20px rgba(66, 184, 131, 0); }
100% { box-shadow: 0 0 0 0 rgba(66, 184, 131, 0); }
}
.pulse {
animation: breathe 2s ease-in-out infinite;
}
核心知识点拆解
知识点 1:transition 与 animation 的核心区别
| 维度 | transition | animation |
|---|---|---|
| 触发方式 | 需要状态变化(hover/JS修改) | 页面加载自动运行 |
| 中间状态 | 仅知道”开始”和”结束” | 通过 @keyframes 定义多个关键帧 |
| 循环能力 | 无(只能单次过渡) | 支持 infinite 无限循环 |
| 方向控制 | 只能正方向 | normal / reverse / alternate |
| 暂停控制 | 无 | 支持 animation-play-state: paused |
| 精确控制 | 只能用 timing-function 控制曲线 | 每帧可定义独立属性和缓动 |
| 适用场景 | 交互反馈(hover/切换显隐) | 持续动画(loading/入场引导) |
面试金句:transition 是”状态到状态”,animation 是”序列到序列”。
知识点 2:timing-function 与贝塞尔曲线
CSS 动画的缓动函数基于三次贝塞尔曲线 cubic-bezier(x1, y1, x2, y2):
1
2
3
4
5
6
7
8
9
/* 预定义值 */
.element { transition: all 0.3s ease; } /* 慢→快→慢 */
.element { transition: all 0.3s ease-in; } /* 慢→快 */
.element { transition: all 0.3s ease-out; } /* 快→慢 */
.element { transition: all 0.3s linear; } /* 匀速 */
.element { transition: all 0.3s ease-in-out; } /* 慢→快→慢(比 ease 更对称) */
/* 自定义:弹跳效果 */
.element { transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); }
💡 面试避坑:ease 和 ease-in-out 的区别——ease 是快入慢出(前半段更快),ease-in-out 是慢入慢出(前后对称)。
知识点 3:step 函数与逐帧动画
对于逐帧动画(类似 GIF),使用 steps() 替代贝塞尔曲线:
1
2
3
4
5
6
7
8
9
10
11
12
/* 精灵图逐帧动画:4 帧,每帧 100px */
.sprite {
width: 100px;
height: 100px;
background: url('sprite.png') 0 0 no-repeat;
animation: run 0.4s steps(4) infinite;
}
@keyframes run {
from { background-position: 0 0; }
to { background-position: -400px 0; }
}
知识点 4:will-change 与硬件加速
核心原理:声明哪些属性会变化,让浏览器提前创建合成层,将动画移交给 GPU:
1
2
3
.element {
will-change: transform, opacity;
}
重要规则:
- 只对即将发生动画的元素使用
- 动画结束后移除(或:使用后删掉)
- 不要对大量元素同时使用(会耗尽 GPU 内存)
1
2
3
4
5
6
7
/* ✅ 正确用法:动画开始前加上,结束后移除 */
.card:hover {
will-change: transform;
}
.card {
transition: transform 0.3s;
}
1
2
3
4
5
6
/* ✅ 更推荐的做法:使用 transform 触发合成层 */
.card {
transform: translateZ(0); /* 触发 GPU 合成层 */
/* 或 */
backface-visibility: hidden;
}
实战案例
案例一:骨架屏加载动画
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.skeleton {
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 37%,
#f0f0f0 63%
);
background-size: 200% 100%;
animation: shimmer 1.5s ease-in-out infinite;
border-radius: 4px;
}
案例二:入场动画序列(交错效果)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.list-item {
opacity: 0; /* 初始隐藏 */
animation: fadeInUp 0.4s ease-out forwards;
}
/* 交错出现:每个 item 延迟递增 */
.list-item:nth-child(1) { animation-delay: 0s; }
.list-item:nth-child(2) { animation-delay: 0.1s; }
.list-item:nth-child(3) { animation-delay: 0.2s; }
.list-item:nth-child(4) { animation-delay: 0.3s; }
案例三:3D 翻转卡片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.card-container {
perspective: 1000px; /* 给父容器设置透视 */
}
.card {
width: 200px;
height: 300px;
transition: transform 0.6s;
transform-style: preserve-3d; /* 保留 3D 空间 */
}
.card:hover {
transform: rotateY(180deg);
}
.card-face {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden; /* 背面隐藏 */
}
.card-back {
transform: rotateY(180deg);
}
底层原理
浏览器渲染管线与 CSS 动画
理解 CSS 动画的性能,关键要理解浏览器的渲染管线:
1
2
3
JavaScript → Style → Layout → Paint → Composite
↑
CSS 动画可以只到这里
触发不同阶段的属性:
| 操作 | 触发 | 举例 |
|---|---|---|
| Layout(重排) | 改变几何尺寸 | width / height / top / left / padding / margin |
| Paint(重绘) | 改变视觉外观 | color / background / box-shadow / border-radius |
| Composite(合成) | 改变变形/透明度 | transform / opacity |
🎯 核心原则:只触发 Composite 的动画性能最好,因为它们在合成器线程执行,不占用主线程。
1
2
3
4
5
6
7
8
9
10
/* ✅ 高性能:仅触发 Composite */
.element {
transform: translateX(100px); /* 只合成 */
/* 等价于 left: 100px 但 left 会触发 Layout */
}
/* ❌ 低性能:触发 Layout */
.element {
left: 100px; /* 触发 Layout → Paint → Composite */
}
合成器线程的工作原理
1
2
3
4
5
6
7
8
9
10
11
12
13
主线程 合成器线程
┌─────────────┐ ┌──────────────┐
│ 执行 JS │ │ 处理滚动 │
│ 计算样式 │ │ 处理 CSS 动画 │
│ 布局(Layout) │ │ 合成图层 │
│ 绘制(Paint) │ └──────────────┘
│ 生成合成层 │ │
└──────┬──────┘ │
│ 提交位图 │
▼ ▼
┌──────────────────────────┐
│ GPU 合成→显示 │
└──────────────────────────┘
关键 insight:transform 和 opacity 动画即使主线程被 JS 阻塞仍然流畅,因为它们完全运行在合成器线程。
高频面试题解析
Q1:transition 和 animation 有什么区别?
参考答案:
| transition | animation |
|---|---|
| 需要触发条件(hover/类名切换) | 页面加载自动执行 |
| 只有开始和结束两个状态 | 通过 @keyframes 定义多个状态 |
| 不能循环 | 支持 infinite 循环 |
| 不能暂停 | 支持 animation-play-state |
应用场景选择:
- 交互反馈(按钮 hover、表单 focus)→ transition
- 持续动画(loading、入场动画、轮播)→ animation
Q2:CSS 动画性能优化的关键点是什么?
参考答案:
- 只使用 transform 和 opacity:这两个属性只触发 Composite,性能最好
- 避免触发 Layout 的属性:不要用 width/height/top/left 做动画
- 使用 will-change 提示浏览器:提前创建合成层
- 控制合成层数量:不要对大量元素使用 will-change,避免 GPU 内存溢出
- 合理使用 requestAnimationFrame:当 JS 动画不可避免时
Q3:transform: translateZ(0) 的作用是什么?
参考答案: 它强制元素创建独立的合成层,将元素提到 GPU 上渲染。这样做的效果:
- 该元素的动画不触发其他元素的重绘
- 动画运行在合成器线程,更流畅
- 缺点是增加 GPU 内存开销
Q4:CSS 动画与 JS 动画如何选择?
参考答案: 优先使用 CSS 动画(性能更好、代码更简洁),遇到以下情况才用 JS:
- 动画逻辑复杂(依赖滚动位置、鼠标位置)
- 需要精细的逐帧控制(canvas/WebGL)
- 需要动态创建或销毁动画
Q5:什么是 “composite only properties”?
参考答案: 指 transform 和 opacity 两个属性——它们的变化不触发 Layout 和 Paint,只触发 Composite 阶段。浏览器可以在合成器线程独立处理这些动画,不与主线程 JS 执行竞争。
总结与扩展
核心要点
- transition = 状态之间的平滑过渡,需要触发条件
- animation + @keyframes = 独立的多阶段动画序列
- 性能 = 只动 transform/opacity,触发 Composite 最轻量
- 合成器线程:CSS 动画可以独立于主线程运行
- will-change / translateZ(0):主动创建合成层的技巧
延伸学习方向
- Web Animations API:JS 控制 CSS 动画的标准 API
- Scroll-driven Animations:CSS 原生滚动驱动的动画(新特性)
- FLIP 动画技术:用 transform 模拟 layout 变化的高性能动画方案
- Canvas 动画:与 CSS 动画互补,适合复杂视觉效果