第24课:动画——@keyframes、animation 属性与性能优化
过渡(Transition)只能从 A 状态变化到 B 状态,且需要触发器(如悬停)。CSS 动画(Animation)则允许定义多个关键帧、自动播放、循环、暂停、反向等复杂行为,是构建丰富交互动效的核心工具。本节课将深入讲解 @keyframes 关键帧规则、animation 八个子属性的精确控制,以及 CSS 动画性能优化的黄金法则——理解浏览器的渲染流水线,才能写出 60fps 的流畅动画。
1. @keyframes:定义动画的剧本
@keyframes 规则用于定义一个动画序列——在各时间点上,元素应具有什么样式。浏览器会自动计算关键帧之间的过渡。
1 | @keyframes slide-in { |
1.1 语法结构
1 | @keyframes <animation-name> { |
- **
animation-name**:动画名称,供animation-name属性引用。命名规则同 CSS 标识符(区分大小写)。 - 选择器列表:可以是
from/to(等同于0%/100%),也可以是百分比(0%、50%、100%等)。百分比必须按数值升序排列(可重复,但会按出现顺序覆盖)。 - CSS 声明块:在该关键帧上应用的样式。并非所有属性都可用于关键帧(见下文)。
1.2 多关键帧示例
1 | @keyframes bounce { |
关键帧的合并:如果多个百分比选择器使用相同的样式,可写在一起:
1 | @keyframes pulse { |
1.3 哪些属性可以在关键帧中动画化?
与过渡类似,关键帧也只接受可动画化的属性(数值型、颜色型、变换等)。此外,关键帧中**不能使用 !important**——它会直接被忽略。
可安全使用的属性:
transformopacitycolor、background-colorfilter- 尺寸属性(
width、height、margin、padding)——但会触发回流,性能差。
不可使用的属性:
- 离散属性(
display、position、float等)。 - 依赖布局计算的属性(
auto值无法动画化)。
2. animation 属性:控制动画的播放
animation 是一个简写属性,可同时设置八个子属性。其完整语法较为复杂,但实际使用中通常只需指定其中几个。
1 | animation: <name> <duration> <timing-function> <delay> <iteration-count> <direction> <fill-mode> <play-state>; |
2.1 八个子属性详解
| 子属性 | 说明 | 默认值 |
|---|---|---|
animation-name |
指定要应用的 @keyframes 名称。none 表示无动画。 |
none |
animation-duration |
动画完成一个周期的时长。单位为 s 或 ms。0s 表示无动画。 |
0s |
animation-timing-function |
时间函数,与 transition-timing-function 完全一致(ease、linear、cubic-bezier()、steps() 等)。 |
ease |
animation-delay |
延迟多久后开始动画。正值延迟开始;负值会使动画立即开始,但直接从对应时刻的状态开始(如 -2s 相当于直接从第 2 秒处播放)。 |
0s |
animation-iteration-count |
播放次数。可为整数或 infinite(无限循环)。也可为小数(如 0.5 播放一半后停止)。 |
1 |
animation-direction |
播放方向。见下文 2.2。 | normal |
animation-fill-mode |
动画执行前后,元素应用哪一帧的样式。见下文 2.3。 | none |
animation-play-state |
控制动画的播放/暂停状态。running(默认)、paused。可通过 JavaScript 切换,实现暂停/恢复。 |
running |
多个动画:用逗号分隔,每个动画可独立设置参数。
1 | .element { |
2.2 animation-direction:播放方向
| 值 | 行为 |
|---|---|
normal |
默认。从 0% 到 100% 正向播放。 |
reverse |
从 100% 到 0% 反向播放。 |
alternate |
奇数遍正向,偶数遍反向(需要 iteration-count > 1)。 |
alternate-reverse |
奇数遍反向,偶数遍正向。 |
2.3 animation-fill-mode:填补样式
fill-mode 控制动画执行前(延迟期间)和执行后(结束后),元素保持哪一帧的样式。
| 值 | 执行前(延迟期) | 执行后(结束后) |
|---|---|---|
none |
无动画样式(元素使用自身 CSS 规则)。 | 立即恢复到自身 CSS 规则。 |
forwards |
同 none。 |
保持最后一帧(100% 或 to)的样式。 |
backwards |
应用第一帧(0% 或 from)的样式。 |
同 none。 |
both |
应用第一帧样式。 | 保持最后一帧样式。 |
经典案例:淡入动画。若不设 forwards 或 both,动画结束后元素会回到 opacity: 0(消失)。
1 | .fade-in { |
2.4 animation-play-state:暂停与恢复
通过这个属性可以在 CSS 中暂停动画,或通过 JavaScript 动态控制。
1 | .paused { |
1 | element.style.animationPlayState = 'paused'; |
注意:在 @keyframes 内部不能直接使用 animation-play-state(它属于播放控制,不是关键帧样式)。
2.5 steps() 时间函数:逐帧动画
steps(n, <direction>) 将动画分为 n 个等间隔台阶,每步之间无过渡——产生“卡顿”或逐帧动画效果。
n:步数。direction:start(每一步的开始即变化)或end(每一步的结束才变化,默认)。
1 | /* 打字机效果:文字逐个出现(配合宽度或 clip 等属性) */ |
3. 动画性能优化:让动画丝滑不卡顿
动画性能的核心在于避免触发布局(Reflow)和绘制(Repaint),将工作交给 GPU 的合成器(Compositor) 处理。
3.1 浏览器渲染流水线回顾
- 布局(Layout):计算元素的几何位置和尺寸。
- 绘制(Paint):填充像素(颜色、阴影、文本等)。
- 合成(Composite):将多个图层合并显示到屏幕。
只有不触发布局和绘制的属性,才能达到 60fps(每帧 16.67ms)的流畅动画。
3.2 最佳动画属性:transform 和 opacity
| 属性 | 触发布局 | 触发绘制 | 仅触发合成 | 性能评级 |
|---|---|---|---|---|
transform |
❌ | ❌ | ✅ | ⭐⭐⭐⭐⭐ |
opacity |
❌ | ❌ | ✅ | ⭐⭐⭐⭐⭐ |
filter |
❌ | ✅ | ❌ | ⭐⭐⭐ |
background-color |
❌ | ✅ | ❌ | ⭐⭐⭐ |
width / height |
✅ | ✅ | ❌ | ⭐ |
top / left |
✅ | ✅ | ❌ | ⭐ |
box-shadow |
❌ | ✅ | ❌ | ⭐⭐ |
原则:所有“视觉变化”类的动画,尽量用 transform 和 opacity 实现。例如,要移动元素,用 transform: translate() 而非 left / top;要隐藏元素,用 opacity 而非 display: none(后者无法动画化且触发回流)。
3.3 will-change:预告浏览器即将发生的变化
will-change 属性告知浏览器哪些属性即将发生变化,浏览器可以提前创建独立的合成层(Composite Layer),避免动画开始时才匆忙创建导致丢帧。
1 | .slide-element { |
使用原则:
- 仅对即将发生动画的元素使用,不要全局滥用。
- 动画结束后及时移除(通过 JS 或在动画完成事件中重置),否则持续占用 GPU 显存。
- 常见的
will-change值:transform、opacity、filter。
3.4 减少重绘范围:contain 属性
contain 属性可限制浏览器的布局、绘制范围,使其仅影响当前元素及其子树,避免波及整个页面。
1 | .animated-container { |
对于动画容器,contain: layout style paint 告诉浏览器:“这个元素内部的变化不会影响外部布局和绘制”,从而显著减少回流和重绘的计算量。
3.5 性能调试工具
- Chrome DevTools Performance 面板:录制动画过程,分析每帧耗时,定位 Layout、Paint、Composite 各阶段的瓶颈。
- Rendering 面板(DevTools → More Tools → Rendering):勾选“Paint Flashing”可高亮显示重绘区域;“Layer Borders”可显示合成层边界。
4. 综合示例:加载骨架屏(Skeleton Screen)
1 | <style> |
原理:通过移动背景位置,模拟光泽滑过骨架线条的效果。动画仅使用 background-position(会触发绘制),但可优化为 transform 模拟(此处为简洁起见用背景位移动画)。更优方案是使用 ::after 伪元素并 translateX。
课后练习
一、概念自测(选择题 / 填空题)
(单选) 以下哪个属性在动画中性能最优,仅触发合成阶段?
A.width
B.background-color
C.transform
D.left(单选) 关于
animation-fill-mode的值both,以下描述正确的是?
A. 动画结束后保持第一帧样式,开始前使用自身样式。
B. 动画开始前应用第一帧样式,结束后保持最后一帧样式。
C. 动画开始前和结束后都恢复为自身样式。
D. 动画结束后应用第一帧样式,开始前保持最后一帧样式。(填空) 要创建一个无限循环播放、偶数次反向播放的动画,应设置
animation-iteration-count: ______和animation-direction: ______。(多选) 以下哪些措施可以提升 CSS 动画性能?
A. 使用transform代替left实现移动。
B. 在动画元素上设置will-change: transform。
C. 为所有元素添加will-change: all。
D. 使用contain: layout style paint限制动画容器的重绘范围。
二、AI 编程任务:编写面向 AI 的提示词
场景:你需要实现一个旋转加载指示器(Spinner)动画。要求如下:
- 一个圆形指示器,中心透明,边框形成圆环。
- 圆环的一部分使用渐变色或两种颜色交替,形成“转圈”视觉效果。
- 动画使用
@keyframes定义,旋转 360 度,循环无限。 - 动画持续时间 0.8 秒,时间函数为
linear(匀速旋转)。 - 同时添加一个“脉冲”效果:指示器的大小在旋转的同时周期性缩放(
scale在 1 和 1.1 之间变化)。 - 使用两个独立的关键帧动画(一个旋转,一个缩放),通过
animation简写逗号分隔同时播放。 - 指示器需设置
will-change: transform提升性能。
任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成符合上述要求的 HTML 和 CSS 代码。提示词中应明确指定两个动画的 @keyframes 定义、animation 的逗号分隔写法、以及性能优化的属性设置。
课后练习答案
一、概念自测答案
C
- 解析:
transform和opacity仅触发合成阶段,由 GPU 处理,性能最高。
- 解析:
B
- 解析:
both等于backwards+forwards,即动画开始前应用第一帧,结束后保持最后一帧。
- 解析:
infinite和alternate- 解析:
infinite表示无限循环,alternate使偶数遍反向播放。
- 解析:
A、B、D
- 解析:
transform代替位置属性可避免回流;will-change提前创建合成层;contain限制布局和绘制范围。C 错误,给所有元素加will-change: all会过度占用 GPU 内存,反而降低性能。
- 解析:
二、AI 编程任务参考答案(提示词示例)
示例提示词:
“请实现一个旋转加载指示器(Spinner),纯 HTML + CSS。要求:
- 指示器为一个
40px × 40px的<div>,圆形(border-radius: 50%),边框4px solid #e0e0e0,其中一侧边框为渐变或彩色(如border-top-color: #3498db),其余透明。- 定义
@keyframes spin { to { transform: rotate(360deg); } },动画spin 0.8s linear infinite。- 同时添加缩放脉冲效果:
@keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.1); } }。- 使用逗号分隔同时应用两个动画:
animation: spin 0.8s linear infinite, pulse 1.2s ease-in-out infinite。- 元素设置
will-change: transform提升性能。- 指示器居中展示(可用 Flexbox 居中在页面上)。
- 直接输出完整 HTML 文件。”