文章

一句话概括

CSS 动画体系由 transition(过渡)animation(动画) 两大核心组成:transition 负责”从 A 到 B 的状态变化”,animation + @keyframes 负责”多阶段、可循环的复杂运动”,两者底层都依赖浏览器的合成器线程来实现高性能渲染。

背景与意义

为什么需要 CSS 动画?

在早期的 Web 开发中,实现动画效果主要依赖 JavaScript + setIntervalrequestAnimationFrame。这种方式虽然灵活,但存在三个核心问题:

  1. 性能瓶颈:JS 动画运行在主线程,与布局、绘制、JS 执行竞争 CPU 时间片
  2. 代码复杂:一个简单的渐入效果需要几十行 JS 代码
  3. 维护困难:多段动画的时间协调、暂停、反转都需要手动管理

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 的核心区别

维度transitionanimation
触发方式需要状态变化(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); }

💡 面试避坑easeease-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 合成→显示      │
    └──────────────────────────┘

关键 insighttransformopacity 动画即使主线程被 JS 阻塞仍然流畅,因为它们完全运行在合成器线程。

高频面试题解析

Q1:transition 和 animation 有什么区别?

参考答案:

transitionanimation
需要触发条件(hover/类名切换)页面加载自动执行
只有开始和结束两个状态通过 @keyframes 定义多个状态
不能循环支持 infinite 循环
不能暂停支持 animation-play-state

应用场景选择:

  • 交互反馈(按钮 hover、表单 focus)→ transition
  • 持续动画(loading、入场动画、轮播)→ animation

Q2:CSS 动画性能优化的关键点是什么?

参考答案:

  1. 只使用 transform 和 opacity:这两个属性只触发 Composite,性能最好
  2. 避免触发 Layout 的属性:不要用 width/height/top/left 做动画
  3. 使用 will-change 提示浏览器:提前创建合成层
  4. 控制合成层数量:不要对大量元素使用 will-change,避免 GPU 内存溢出
  5. 合理使用 requestAnimationFrame:当 JS 动画不可避免时

Q3:transform: translateZ(0) 的作用是什么?

参考答案:强制元素创建独立的合成层,将元素提到 GPU 上渲染。这样做的效果:

  1. 该元素的动画不触发其他元素的重绘
  2. 动画运行在合成器线程,更流畅
  3. 缺点是增加 GPU 内存开销

Q4:CSS 动画与 JS 动画如何选择?

参考答案: 优先使用 CSS 动画(性能更好、代码更简洁),遇到以下情况才用 JS:

  • 动画逻辑复杂(依赖滚动位置、鼠标位置)
  • 需要精细的逐帧控制(canvas/WebGL)
  • 需要动态创建或销毁动画

Q5:什么是 “composite only properties”?

参考答案:transformopacity 两个属性——它们的变化不触发 Layout 和 Paint,只触发 Composite 阶段。浏览器可以在合成器线程独立处理这些动画,不与主线程 JS 执行竞争。

总结与扩展

核心要点

  1. transition = 状态之间的平滑过渡,需要触发条件
  2. animation + @keyframes = 独立的多阶段动画序列
  3. 性能 = 只动 transform/opacity,触发 Composite 最轻量
  4. 合成器线程:CSS 动画可以独立于主线程运行
  5. will-change / translateZ(0):主动创建合成层的技巧

延伸学习方向

  • Web Animations API:JS 控制 CSS 动画的标准 API
  • Scroll-driven Animations:CSS 原生滚动驱动的动画(新特性)
  • FLIP 动画技术:用 transform 模拟 layout 变化的高性能动画方案
  • Canvas 动画:与 CSS 动画互补,适合复杂视觉效果

相关主题

本文由作者按照 CC BY 4.0 进行授权

© 独行的风. 保留部分权利。

本站采用 Jekyll 主题 Chirpy

本站总访问量 本站访客数 本文阅读量