CSS 动画
过渡(transition)教的是触发时的 A → B。变换(transform)教的是绘制盒子往哪走。动画写的是时间线:状态点轻轻脉动、加载圈一直转、卡片依次入场 —— 步骤写在 @keyframes,用 animation 播放。
动画是什么
两部分:按百分比(或 from / to)列出样式的命名 keyframe 集合(@keyframes),以及用时长、可选延迟、循环次数、方向、缓动函数和 fill-mode 播放该名称的规则。浏览器在 keyframe 停靠点之间插值。
名称必须严格对应:animation-name: pulse 只有在存在 @keyframes pulse 时才会动。只改一边、不改另一边 —— 画面会静悄悄。
动画控制一览
📊 animation 核心组成
| 部分 | 作用 |
|---|---|
@keyframes name { … } |
在 % / from / to 上的属性值时间线 |
animation-name |
播放哪套 keyframe |
animation-duration |
一个周期多长(s 或 ms) |
animation-timing-function |
周期内的缓动(ease、linear 等) |
animation-delay |
第一个周期开始前的等待 |
animation-iteration-count |
循环几次(number 或 infinite) |
animation-direction |
normal、reverse、alternate、alternate-reverse |
animation-fill-mode |
动画前/后的样式(none、forwards、backwards、both) |
animation shorthand |
name → duration → timing → delay → iteration → direction → fill-mode(常见顺序) |
不必一次写全所有 longhand。先从 name + duration 起步;加载器加 infinite,回弹感用 alternate,最后一帧要留住就加 forwards。
第一条时间线 — 温和的脉冲
opacity 和 transform 对绘制更友好。柔和脉冲是第一次写 @keyframes 的经典练习。
@keyframes pulse {
0%,
100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.55;
transform: scale(0.92);
}
}
.status-dot {
display: inline-block;
width: 0.75rem;
height: 0.75rem;
border-radius: 50%;
background: #16a34a;
animation: pulse 1.4s ease-in-out infinite;
}
0% 与 100% 一致,循环才能无缝衔接。infinite 让周期一直转 —— 小状态点很合适;若 animate 整页 hero,容易显得吵。
无限旋转 — 加载器偏爱 linear
linear 加 infinite 的旋转读起来像连续 spinner。记得配 prefers-reduced-motion 降级:隐藏或停转。
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.loader {
width: 2rem;
height: 2rem;
border: 3px solid #e5e7eb;
border-top-color: #2563eb;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
.loader {
animation: none;
border-top-color: #e5e7eb;
}
}
to 是 100% 的简写。只关心转完一圈时,从默认的 0%(无 transform)起步就够。
延迟与错落 — 同一套 keyframes,不同起点
一套 keyframes 可以驱动多个元素。用 animation-delay 做 stagger,列表会有先后顺序,不必写三套动画。
@keyframes fade-up {
from {
opacity: 0;
transform: translateY(0.75rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.reveal-list li {
opacity: 0;
animation: fade-up 0.45s ease forwards;
}
.reveal-list li:nth-child(1) { animation-delay: 0s; }
.reveal-list li:nth-child(2) { animation-delay: 0.08s; }
.reveal-list li:nth-child(3) { animation-delay: 0.16s; }
forwards 在周期结束后保留最后一帧样式 —— 否则元素可能弹回基础规则里的 opacity: 0。这就是 animation-fill-mode 在干活。
一次性入场与 fill-mode
入场动效常只播一次。fill-mode 决定延迟前、最后一帧后是否应用 keyframe 样式。
@keyframes card-in {
from {
opacity: 0;
transform: translateY(1rem) scale(0.98);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.card-enter {
animation: card-in 0.5s ease 0.1s both;
}
此处的 shorthand:名称 card-in,时长 0.5s,缓动 ease,延迟 0.1s,fill-mode both。both 在延迟期间用第一帧,结束后留住最后一帧。
方向、播放状态与何时别用动画
animation-direction: alternate 让奇数周期正向、偶数周期反向 —— 轻柔回弹而不用复制 keyframes。animation-play-state: paused 可冻结正在跑的动画(装饰循环 hover 暂停等)。
简单 hover 换色继续用 transition。需要循环、多步叙事或不绑单一状态切换的运动时再用 animation。keyframes 里优先 transform 和 opacity;animate width、height 或 top 更重,还常抖布局。
自己动手
- 只用 opacity 做三停靠(
0%/50%/100%)脉冲;再加一点scale。 - 用
spin+linear+infinite做 spinner;改成ease感受不均匀。 - 同一
fade-upkeyframes,延迟0s、0.1s、0.2s错落三个 badge —— 对比有无forwards。 - 面板用
animation: card-in 0.5s ease 0.15s both;去掉both看 snap-back。 - 在
prefers-reduced-motion: reduce下对装饰循环设animation: none(或短 fade)。
常见错误
- 指望 hover 的 A→B 淡入会「循环」 —— 那是 animation 的事,不是 lone transition
animation-name与@keyframes名拼写不一致 —— 静默 no-op- 大块醒目区域
infinite却没有暂停或 reduced-motion 方案 - transform/opacity 够用却去 animate 布局属性(
width、margin、top) - 一次性 reveal 基础规则已是
opacity: 0却漏forwards/both
小结
@keyframes定义时间线;animation(或 longhand)播放- iteration、direction、delay、fill-mode 管循环、回弹、错落与结尾留住
- 优先 transform/opacity;装饰动效尊重 prefers-reduced-motion
- 简单状态变化用 transition;多步与重复用 animation
- Effects & Motion 下一课:CSS Filters
🧠 测试您的知识
测试您的知识
通过这个互动测验挑战自己,看看你对这个主题的理解程度如何
📝 说明
- 仔细阅读每个问题
- 为每个问题选择最佳答案
- 您可以随时重新参加测验
- 您的进度将显示在顶部