CSS 动画两大类:transition 与 animation
CSS 做动画有两套工具:transition(过渡)和 animation + keyframes(关键帧动画)。transition 是简单场景——状态从 A 变到 B,中间平滑过渡;animation 是复杂场景——多帧循环、入场、精细控制的长动画。
打个比方:transition 像是开门——你指定门从关到开怎么过渡,浏览器自动算中间帧;animation 像是拍动画——你一帧一帧定义每个时间点门在什么位置,浏览器按你的脚本播放。
能用 transition 解决的不要用 animation。transition 声明更少、性能更好,覆盖了悬停变色、位置移动、大小缩放 80% 的交互动效。
- transition属性变化的平滑过渡,一行代码搞定悬停动效。
- @keyframes定义多帧动画脚本,配合 animation 属性播放。
- cubic-bezier自定义缓动曲线,决定动画的"手感"。
- transform/opacity唯一走 GPU 加速的两个属性,动画必用。
transition:状态变化的平滑过渡
transition 最常见的用途是悬停效果。默认情况下,鼠标移到按钮上,颜色瞬间变化——加上 transition 后,颜色在 0.2 秒内平滑过渡,手感立刻精致起来。
.btn {
background: #1E5FA8;
transition: background 0.2s ease, transform 0.2s ease;
}
.btn:hover {
background: #164A85;
transform: translateY(-2px);
}
transition 的四个参数:要过渡的属性、时长、缓动函数、延迟。可以同时过渡多个属性,用逗号分隔。transition: all 0.2s ease 是简写,但性能不如只指定需要过渡的属性。
| 属性 | 作用 | 常用值 |
|---|---|---|
| transition-property | 哪些属性参与过渡 | background, transform, opacity |
| transition-duration | 过渡时长 | 0.2s, 0.3s |
| transition-timing-function | 缓动曲线 | ease, linear, ease-in-out |
| transition-delay | 延迟多久开始 | 0s, 0.1s |
transition 是单向的:鼠标移开时它也会反向过渡回去——不需要你写 hover 以外的代码。这就是它比 animation 简单的地方。
keyframes:多帧复杂动画
当动画需要多帧、循环、或者自动播放(不需要用户触发),就要用 @keyframes。它定义一个动画脚本,从 0% 到 100% 分阶段描述每个时间点的状态。
@keyframes bounce {
0% { transform: translateY(0); }
50% { transform: translateY(-20px); }
100% { transform: translateY(0); }
}
.loading-ball {
animation: bounce 1s ease infinite;
}
这段代码让小球在垂直方向上下弹跳,1 秒一个周期,无限循环。animation 属性把动画名、时长、缓动、循环次数串起来。
animation 的常用参数和 transition 类似,但多了两个:animation-iteration-count(infinite 表示无限循环)和 animation-direction(alternate 表示来回播放)。
@keyframes fadeSlideIn {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.card {
animation: fadeSlideIn 0.4s ease forwards;
}
forwards 表示动画结束后保持最终状态。入场动画最常用这个——页面加载时卡片从左侧淡入滑入,停在最终位置。
cubic-bezier:缓动函数决定手感
同样一段动画,缓动曲线不同,手感天差地别。ease 是默认值——开始快、结束慢;linear 匀速——像钟表指针;ease-in-out 两头慢中间快。
想要更精致的手感,用 cubic-bezier() 自定义贝塞尔曲线。四个参数是两个控制点的坐标:
/* 常见的精致缓动 */
transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
/* 回弹效果(略微超出目标再回来) */
transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
第二条曲线的 y 值超过了 1,动画会略微超出目标位置再弹回来——这就是"回弹"手感。做按钮点击、卡片悬停时用这种曲线,动效会显得很有弹性。
有一个在线工具叫 cubic-bezier.com,可以拖动点直观调曲线,调好后复制 CSS 代码。做动效时强烈建议用这种工具,不要凭感觉写数字。
transform 与 opacity:性能最优属性
不是所有 CSS 属性都适合做动画。改 width、height、left、top 会触发浏览器重新布局(reflow),每帧都要重算整个页面的排版——非常卡。改 color、background 会触发重绘(repaint)——也不便宜。
只有 transform 和 opacity 两个属性走 GPU 加速:它们在合成层处理,不触发重排和重绘,帧率稳定 60fps。
| 要做的动效 | 错误写法 | 正确写法 |
|---|---|---|
| 移动元素 | left: 100px | transform: translateX(100px) |
| 缩放元素 | width: 120% | transform: scale(1.2) |
| 淡入淡出 | color: rgba(0,0,0,0.5) | opacity: 0.5 |
| 旋转元素 | — | transform: rotate(45deg) |
记住一个原则:动画只动 transform 和 opacity。位置用 translate、大小用 scale、旋转用 rotate、透明度用 opacity——全部走 GPU,丝滑流畅。
交互动效实战:悬停、加载、入场
三个最常用的交互动效模式:
1. 卡片悬停抬升:鼠标移上去卡片微微上浮,加阴影。
.card {
transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
2. 加载旋转环:数据加载时一个圆环不停转。
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner {
border: 3px solid #e0e0e0;
border-top-color: #1E5FA8;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
3. 列表项依次入场:页面加载时,列表项一个个淡入,不是同时出现。
.list-item {
opacity: 0;
animation: fadeIn 0.4s ease forwards;
}
.list-item:nth-child(1) { animation-delay: 0.05s; }
.list-item:nth-child(2) { animation-delay: 0.1s; }
.list-item:nth-child(3) { animation-delay: 0.15s; }
用 nth-child 给每个列表项加递增的延迟,就形成了依次入场的效果。这种"瀑布流入场"比一次性全显示精致得多。
性能优化与无障碍降级
动画做太多会伤性能,也要照顾对动效敏感的用户:
- prefers-reduced-motion:部分用户(前庭功能障碍)在系统设置里开了"减少动画",CSS 里用媒体查询关掉动画:
@media (prefers-reduced-motion: reduce) { * { animation: none; transition: none; } }。 - 不要同时跑太多动画:一个页面上 10 个元素同时做 transform 动画,GPU 也会吃力。关键路径上的动效保留,装饰性的砍掉。
- will-change 提示浏览器:对将要做动画的元素加
will-change: transform,浏览器提前把它提到合成层。但不要滥用——加太多反而占内存。 - 时长控制:交互动效 0.2–0.3 秒最佳,超过 0.5 秒用户会觉得慢。入场动画可以稍长,但不要超过 1 秒。
好的动效是"感觉不到但用着舒服"的——它引导注意力、提供反馈、让界面有生命力。过度动效则是炫技,反而分散用户注意力。克制比炫技更重要。