深入理解 CSS 动画完整体系
transition、animation、Web Animations API、滚动驱动动画、View Transitions 五套系统对比
什么是 CSS 动画?
定义:CSS 动画是浏览器提供的声明式运动系统,通过描述元素属性值随时间变化的方式来产生视觉效果。CSS 目前有五套动画机制:transition(过渡)、@keyframes + animation(关键帧动画)、Web Animations API(JS 控制)、Scroll-Driven Animations(滚动驱动)和 View Transitions API(页面过渡)。
涉及场景:
- 交互反馈:按钮 hover 变色、菜单展开收起、模态框淡入淡出
- 加载指示:骨架屏闪烁、旋转加载器、进度条动画
- 滚动体验:滚动进度条、视差效果、元素进入视口时的渐入
- 页面过渡:SPA 路由切换动画、MPA 跨页面过渡(View Transitions)
- 数据可视化:图表数据更新时的平滑过渡
- 游戏 / 创意:Canvas 动画循环、粒子效果、3D 变换
作用:
- 提升用户体验:流畅的动画让界面更有"质感",减少操作的突兀感
- 引导用户注意力:通过运动吸引用户关注重要信息或操作入口
- 性能关键:只动画
transform+opacity可在合成线程运行,不阻塞主线程 - 面试常考:transition 与 animation 区别、性能优化(will-change / 合成层)是高频考点
一、Transition(过渡)
最简单的动画方式——属性值从 A 变到 B 时自动产生过渡效果。
css
.box {
width: 100px;
background: blue;
/* 完整语法 */
transition-property: width, background; /* 过渡哪些属性 */
transition-duration: 0.3s, 0.5s; /* 过渡时长 */
transition-timing-function: ease-in-out; /* 缓动函数 */
transition-delay: 0s, 0.1s; /* 延迟 */
transition-behavior: allow-discrete; /* 允许离散属性过渡(新) */
/* 简写 */
transition: width 0.3s ease-in-out, background 0.5s ease 0.1s;
/* 或 */
transition: all 0.3s ease;
}
.box:hover {
width: 200px;
background: red;
}缓动函数完整列表
css
/* 预定义关键字 */
transition-timing-function: ease; /* 默认,慢-快-慢 */
transition-timing-function: linear; /* 匀速 */
transition-timing-function: ease-in; /* 慢-快(加速) */
transition-timing-function: ease-out; /* 快-慢(减速) */
transition-timing-function: ease-in-out; /* 慢-快-慢 */
/* 贝塞尔曲线 */
transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55); /* 弹性效果 */
/* 阶梯函数 */
transition-timing-function: steps(4, jump-start);
transition-timing-function: steps(4, jump-end); /* 等价于 step-end */
transition-timing-function: steps(4, jump-both);
transition-timing-function: steps(4, jump-none);
transition-timing-function: step-start; /* = steps(1, jump-start) */
transition-timing-function: step-end; /* = steps(1, jump-end) */
/* 线性缓动函数(CSS 新特性) */
transition-timing-function: linear(0, 0.25 75%, 1);
/* 可以定义任意曲线!更灵活 */
/* 弹簧效果(通过 linear() 实现) */
transition-timing-function: linear(
0, 0.006, 0.025, 0.058, 0.104, 0.163, 0.234, 0.315, 0.406,
0.503, 0.604, 0.705, 0.804, 0.896, 0.979, 1.05, 1.107,
1.15, 1.176, 1.189, 1.189, 1.178, 1.158, 1.132, 1.101,
1.069, 1.038, 1.01, 0.986, 0.966, 0.952, 0.943, 0.939,
0.939, 0.943, 0.95, 0.96, 0.971, 0.983, 0.994, 1.004,
1.012, 1.017, 1.02, 1.02, 1.018, 1.013, 1.007, 1.001, 1
);可过渡属性
css
/* 大多数数值型属性可以过渡 */
/* ✅ 可过渡 */
width, height, margin, padding, border-width
top, left, right, bottom
opacity
color, background-color, border-color
transform
box-shadow, text-shadow
font-size, letter-spacing, line-height
/* ❌ 不可过渡(离散值) */
display /* 但 transition-behavior: allow-discrete 后可以! */
visibility /* 可以过渡(visible ↔ hidden) */
position
float
overflow
/* 新特性:离散属性过渡(2024+) */
.modal {
display: none;
opacity: 0;
transition: display 0.3s allow-discrete, opacity 0.3s;
}
.modal.open {
display: block;
opacity: 1;
}
/* @starting-style 定义初始状态 */
@starting-style {
.modal.open {
opacity: 0;
}
}Transition 事件
javascript
element.addEventListener('transitionstart', (e) => {
console.log(`${e.propertyName} 开始过渡`);
});
element.addEventListener('transitionend', (e) => {
console.log(`${e.propertyName} 过渡完成,耗时 ${e.elapsedTime}s`);
});
element.addEventListener('transitioncancel', (e) => {
console.log(`${e.propertyName} 过渡被取消`);
});
element.addEventListener('transitionrun', (e) => {
console.log(`${e.propertyName} 过渡启动(包括delay期间)`);
});二、Animation(关键帧动画)
比 transition 更强大——可以定义多个关键帧、循环播放、暂停控制。
css
/* 定义关键帧 */
@keyframes bounce {
0% {
transform: translateY(0);
animation-timing-function: ease-out;
}
50% {
transform: translateY(-30px);
animation-timing-function: ease-in;
}
100% {
transform: translateY(0);
}
}
/* 应用动画 */
.element {
/* 完整属性 */
animation-name: bounce; /* 关键帧名称 */
animation-duration: 1s; /* 时长 */
animation-timing-function: ease; /* 缓动(可在关键帧中覆盖) */
animation-delay: 0.5s; /* 延迟 */
animation-iteration-count: infinite; /* 循环次数(infinite = 无限) */
animation-direction: alternate; /* 方向 */
animation-fill-mode: forwards; /* 结束后保持状态 */
animation-play-state: running; /* running | paused */
animation-composition: replace; /* replace | add | accumulate */
animation-timeline: auto; /* auto | scroll() | view() */
/* 简写 */
animation: bounce 1s ease 0.5s infinite alternate forwards;
}animation-direction
css
animation-direction: normal; /* 正向播放 */
animation-direction: reverse; /* 反向播放 */
animation-direction: alternate; /* 交替:正-反-正-反... */
animation-direction: alternate-reverse; /* 交替:反-正-反-正... */animation-fill-mode
css
animation-fill-mode: none; /* 默认:动画前后不应用关键帧样式 */
animation-fill-mode: forwards; /* 动画结束后保持最后一帧 */
animation-fill-mode: backwards; /* delay期间应用第一帧 */
animation-fill-mode: both; /* forwards + backwards */animation-composition(新特性)
css
/* 多个动画叠加时如何组合 */
@keyframes slide { to { transform: translateX(100px); } }
@keyframes scale { to { transform: scale(1.5); } }
.element {
transform: rotate(45deg); /* 基础值 */
animation: slide 1s, scale 1s;
animation-composition: add; /* 叠加而非替换 */
/* 最终:rotate(45deg) translateX(100px) scale(1.5) */
}Animation 事件
javascript
element.addEventListener('animationstart', (e) => {
console.log(`动画 ${e.animationName} 开始`);
});
element.addEventListener('animationiteration', (e) => {
console.log(`动画 ${e.animationName} 完成一次迭代`);
});
element.addEventListener('animationend', (e) => {
console.log(`动画 ${e.animationName} 结束`);
});
element.addEventListener('animationcancel', (e) => {
console.log(`动画 ${e.animationName} 被取消`);
});三、Web Animations API(WAAPI)
用 JavaScript 控制动画,与 CSS 动画底层共享同一引擎。
javascript
// 基本用法
const animation = element.animate(
// 关键帧(等价于 @keyframes)
[
{ transform: 'translateX(0)', opacity: 1 },
{ transform: 'translateX(300px)', opacity: 0 }
],
// 选项
{
duration: 1000, // 毫秒
easing: 'ease-in-out',
delay: 0,
iterations: Infinity, // 循环次数
direction: 'alternate',
fill: 'forwards',
composite: 'replace' // replace | add | accumulate
}
);
// 控制方法
animation.play(); // 播放
animation.pause(); // 暂停
animation.reverse(); // 反向
animation.cancel(); // 取消
animation.finish(); // 跳到结尾
animation.persist(); // 持久化(防止被GC)
// 属性
animation.currentTime; // 当前时间(ms)
animation.playbackRate; // 播放速率(2 = 2倍速,-1 = 反向)
animation.playState; // 'idle' | 'running' | 'paused' | 'finished'
animation.startTime; // 开始时间
animation.effect; // 动画效果对象
// Promise
animation.finished.then(() => {
console.log('动画完成');
});
animation.ready.then(() => {
console.log('动画准备好了');
});
// 获取元素上的所有动画
const animations = element.getAnimations();
// 获取包括子元素的动画
const allAnimations = element.getAnimations({ subtree: true });
// 获取文档上的所有动画
const docAnimations = document.getAnimations();
// 序列动画
async function sequentialAnimations(element) {
await element.animate(
[{ opacity: 0 }, { opacity: 1 }],
{ duration: 500, fill: 'forwards' }
).finished;
await element.animate(
[{ transform: 'scale(1)' }, { transform: 'scale(1.2)' }],
{ duration: 300, fill: 'forwards' }
).finished;
await element.animate(
[{ transform: 'scale(1.2)' }, { transform: 'scale(1)' }],
{ duration: 300, fill: 'forwards' }
).finished;
}四、滚动驱动动画(Scroll-Driven Animations)
2024年新特性,用滚动进度驱动动画,无需 JavaScript。
css
/* 基于滚动进度的动画 */
@keyframes progressBar {
from { width: 0%; }
to { width: 100%; }
}
.progress {
position: fixed;
top: 0;
height: 3px;
background: blue;
animation: progressBar linear;
animation-timeline: scroll(); /* 绑定到最近的滚动容器 */
}
/* scroll() 函数参数 */
animation-timeline: scroll(); /* 默认:最近的块级滚动祖先 */
animation-timeline: scroll(root); /* 根滚动容器(viewport) */
animation-timeline: scroll(nearest); /* 最近的滚动祖先 */
animation-timeline: scroll(self); /* 自身滚动 */
animation-timeline: scroll(root block); /* 指定轴:block / inline / x / y */
/* 基于元素可见性的动画 */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(50px); }
to { opacity: 1; transform: translateY(0); }
}
.card {
animation: fadeIn linear both;
animation-timeline: view(); /* 元素进入/离开视口时触发 */
animation-range: entry 0% entry 100%; /* 进入视口时播放 */
}
/* animation-range 值 */
animation-range: entry; /* 元素进入视口 */
animation-range: exit; /* 元素离开视口 */
animation-range: contain; /* 元素完全在视口中 */
animation-range: cover; /* 从开始进入到完全离开 */
animation-range: entry 25% exit 75%; /* 自定义范围 */
/* 命名时间线(多个元素共享) */
.scroller {
scroll-timeline-name: --my-timeline;
scroll-timeline-axis: block;
}
.animated-element {
animation-timeline: --my-timeline;
}
/* 视图时间线命名 */
.target {
view-timeline-name: --card-timeline;
view-timeline-axis: block;
}五、View Transitions API
页面状态变化时的平滑过渡动画。
css
/* 基本的 View Transition 样式 */
::view-transition-old(root) {
animation: fadeOut 0.3s ease;
}
::view-transition-new(root) {
animation: fadeIn 0.3s ease;
}
/* 为特定元素命名 */
.hero-image {
view-transition-name: hero;
}
/* 自定义特定元素的过渡 */
::view-transition-old(hero) {
animation: scaleDown 0.4s ease;
}
::view-transition-new(hero) {
animation: scaleUp 0.4s ease;
}
/* 跨文档 View Transition(MPA) */
@view-transition {
navigation: auto;
}javascript
// SPA 中的 View Transition
async function navigate(url) {
const transition = document.startViewTransition(async () => {
// 更新 DOM
const response = await fetch(url);
const html = await response.text();
document.querySelector('main').innerHTML = html;
});
// 等待过渡完成
await transition.finished;
}
// 自定义过渡类型
document.startViewTransition({
update: () => updateDOM(),
types: ['slide-left'] // 可以根据类型应用不同 CSS
});动画性能优化
css
/* 1. 只动画合成属性(transform + opacity) */
/* ✅ 不触发重排(reflow)和重绘(repaint) */
transform: translateX() / scale() / rotate()
opacity: 0-1
/* ❌ 触发重排(代价最高) */
width, height, margin, padding, top, left
/* ❌ 触发重绘 */
color, background, box-shadow, border
/* 2. will-change 提示浏览器 */
.animated {
will-change: transform, opacity;
/* 浏览器会提前创建合成层 */
/* ⚠️ 不要过度使用,每个合成层消耗内存 */
}
/* 3. 使用 transform: translateZ(0) 或 translate3d 强制GPU加速 */
.gpu-accelerated {
transform: translateZ(0);
}
/* 4. 减少动画元素数量 */
/* 5. 使用 requestAnimationFrame 替代 setInterval */
/* 6. 使用 CSS 动画替代 JS 动画(浏览器可以优化) */
/* 7. contain 属性限制重绘范围 */
.card {
contain: layout paint;
}
/* 8. content-visibility 延迟渲染 */
.offscreen {
content-visibility: auto;
contain-intrinsic-size: 200px;
}五套动画系统对比
| 特性 | transition | animation | WAAPI | 滚动驱动 | View Transition |
|---|---|---|---|---|---|
| 触发方式 | 状态变化 | 自动/类名 | JS调用 | 滚动 | DOM更新 |
| 关键帧 | 2个(起止) | 多个 | 多个 | 复用@keyframes | 自动生成 |
| 循环 | ❌ | ✅ | ✅ | 跟随滚动 | ❌ |
| JS控制 | 有限 | 有限 | ✅ 完全 | 有限 | ✅ |
| 暂停/恢复 | ❌ | ✅ | ✅ | N/A | ❌ |
| 性能 | 好 | 好 | 好 | 极好 | 好 |
| 适用场景 | hover/状态切换 | 装饰性动画 | 复杂交互 | 滚动效果 | 页面过渡 |
总结
CSS 动画核心知识点:
┌──────────────────────────────────────────────────────────┐
│ Transition │
│ • 最简单的 A→B 过渡,适合 hover/状态切换 │
│ • transition-behavior: allow-discrete 可过渡 display │
│ • @starting-style 定义进入动画的初始状态 │
├──────────────────────────────────────────────────────────┤
│ Animation │
│ • @keyframes 多关键帧,支持循环、方向、暂停 │
│ • animation-composition 控制多动画叠加方式 │
├──────────────────────────────────────────────────────────┤
│ Web Animations API │
│ • element.animate() JS控制动画 │
│ • play/pause/reverse/cancel/finish 完整控制 │
│ • 序列动画用 async/await + .finished │
├──────────────────────────────────────────────────────────┤
│ 滚动驱动动画 │
│ • animation-timeline: scroll() / view() │
│ • 无需 JS,纯 CSS 实现滚动进度条、滚动视差 │
├──────────────────────────────────────────────────────────┤
│ 性能 │
│ • 只动画 transform + opacity(合成属性) │
│ • 合理使用 will-change 和 contain │
│ • CSS 动画优于 JS 动画(浏览器可优化到合成线程) │
└──────────────────────────────────────────────────────────┘