2、CSS动画基础:Transition与Animation详解、关键帧设计、贝塞尔曲线调优、硬件加速原理
好,咱们正式开始聊CSS动画。说实话,很多前端同学做了三五年,CSS动画还停留在「鼠标移上去变个颜色」的水平。这其实挺可惜的。CSS动画看着简单,但真要做出丝滑、高性能、可维护的企业级动效,里面的门道可不少。
我个人习惯把CSS动画分成两派:Transition 和 Animation。这两兄弟各有各的脾气,用对了地方事半功倍,用错了……嗯,我踩过的坑后面会讲。
2.1 Transition:最简单的入场动画
Transition,说白了就是「状态变化的过渡」。你给一个元素定义两个状态,它就能在切换时自动补间。比如鼠标悬停、类名切换、表单聚焦,这些都是触发时机。
/* 基础写法 */
.box {
width: 100px;
height: 100px;
background: #4A90D9;
transition: all 0.3s ease;
}
.box:hover {
width: 200px;
background: #D94A4A;
}
这里有个细节——all 虽然方便,但我建议你尽量别用。为什么?因为浏览器需要监听所有属性变化,性能开销大。我在项目中遇到过,一个列表里几百个卡片都用 transition: all,结果滚动时掉帧掉得厉害。改成具体属性名,帧率立马就稳了。
transition: transform 0.3s ease, opacity 0.2s linear;
2.2 Animation & @keyframes:更自由的动画控制
Transition 只能做「从A到B」的简单过渡。但如果你想要「A→B→C→D」这种多段动画,或者想让动画自动循环播放,那就得上 Animation 了。
Animation 的核心是 @keyframes。你可以把它理解成动画的分镜头脚本——每一帧长什么样,你说了算。
@keyframes pulse {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.2); opacity: 0.7; }
100% { transform: scale(1); opacity: 1; }
}
.pulse-btn {
animation: pulse 2s ease-in-out infinite;
}
你想想看,这种「呼吸灯」效果,用 Transition 就很难实现。因为你需要一个循环的、自动执行的动画,而不是靠用户交互触发。
- 关键帧数量不是越多越好。3-5个关键帧通常就够用,太多反而让动画变「肉」。
- 0% 和 100% 最好都写上,除非你明确知道元素初始状态就是默认值。
- 百分比可以不是整数,比如 12.5% 也是合法的,但可读性差,不推荐。
2.3 贝塞尔曲线调优:让动画有「灵魂」
很多人觉得贝塞尔曲线就是 ease、linear、ease-in-out 这几个预设值。其实不然。真正的调优,是手动写 cubic-bezier()。
我记得有一次做弹窗动画,用 ease-out 总觉得「弹出来那一下不够干脆」。后来调成了 cubic-bezier(0.34, 1.56, 0.64, 1)——前面加速快,后面带一点弹性过冲。效果立马就对了。
/* 常用贝塞尔曲线参考 */
/* 缓入缓出 - 标准 */
cubic-bezier(0.42, 0, 0.58, 1)
/* 弹性弹入 - 适合弹窗出现 */
cubic-bezier(0.34, 1.56, 0.64, 1)
/* 快速进入 + 缓慢结束 - 适合通知提示 */
cubic-bezier(0.25, 0.1, 0.25, 1)
/* 回弹效果 - 适合拖拽松手 */
cubic-bezier(0.68, -0.55, 0.27, 1.55)
调优贝塞尔曲线,我建议你打开 Chrome DevTools 的「Animations」面板。选中元素,直接在曲线编辑器上拖拽控制点,实时预览效果。比你在代码里改数字快多了。
2.4 硬件加速原理:为什么 GPU 比 CPU 快?
好,接下来聊点底层的。为什么有些动画丝滑,有些动画掉帧?关键就在「硬件加速」这四个字上。
浏览器渲染一个页面,大致分这么几步:JavaScript → Style → Layout → Paint → Composite。其中 Layout(布局计算)和 Paint(绘制)是最耗时的。
如果你用 left、top、width、height 这些属性做动画,每次变化都会触发 Layout 重算。浏览器得重新计算所有元素的位置,然后重新绘制——这活儿全压在 CPU 上。
但如果你用 transform 和 opacity,情况就不同了。这两个属性只触发 Composite(合成)阶段。浏览器会把元素单独提升到一个「合成层」,交给 GPU 来处理。GPU 最擅长的就是矩阵运算和透明度混合,处理起来比 CPU 快一个数量级。
- 能用
transform实现的位移,绝不用left/top。 - 能用
opacity实现的显隐,绝不用display/visibility。 - 能用
scale实现的缩放,绝不用width/height。
/* ❌ 不推荐 - 触发 Layout */
.box {
left: 0;
transition: left 0.3s;
}
.box.active {
left: 100px;
}
/* ✅ 推荐 - 只触发 Composite */
.box {
transform: translateX(0);
transition: transform 0.3s;
}
.box.active {
transform: translateX(100px);
}
你可能会问:「那我怎么知道当前动画有没有触发硬件加速?」很简单,打开 Chrome DevTools 的「Rendering」面板,勾选「Layer borders」。如果元素周围有橙色边框,说明它已经被提升到合成层了。
will-change: transform。这相当于提前告诉浏览器:「这个元素要动,你提前准备好合成层。」但别滥用,每个合成层都会占用 GPU 内存。
2.5 实战中的取舍
最后聊点实际的。在企业级项目中,你不可能所有动画都用 CSS 搞定。我的经验是:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 简单的悬停/点击反馈 | Transition | 代码简洁,性能好 |
| 循环播放的装饰性动画 | Animation + @keyframes | 可控性强,支持循环 |
| 页面入场/离场动画 | Animation + JS 控制类名 | 方便与路由系统结合 |
| 复杂路径动画(曲线运动) | JS 动画库(如 GSAP) | CSS 对路径动画支持有限 |
| 高性能滚动动画 | IntersectionObserver + CSS | 避免 JS 频繁计算 |
嗯,CSS 动画基础就聊这么多。记住一句话:能用 transform 和 opacity 解决的,就别折腾其他属性。这是我从无数次掉帧教训里总结出来的铁律。
下一章咱们会深入聊 JS 动画引擎的设计思路,到时候你会看到,即便是最复杂的动画需求,底层原理也离不开今天讲的这些基础。