正在加载...

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 做柔和状态脉冲

css
@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 降级:隐藏或停转。

无限 rotate 的加载环

css
@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,列表会有先后顺序,不必写三套动画。

列表项错落 fade-up

css
@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 样式。

fill-mode both 的卡片入场

css
@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 更重,还常抖布局。

自己动手

  1. 只用 opacity 做三停靠(0% / 50% / 100%)脉冲;再加一点 scale。
  2. 用 spin + linear + infinite 做 spinner;改成 ease 感受不均匀。
  3. 同一 fade-up keyframes,延迟 0s、0.1s、0.2s 错落三个 badge —— 对比有无 forwards。
  4. 面板用 animation: card-in 0.5s ease 0.15s both;去掉 both 看 snap-back。
  5. 在 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

🧠 测试您的知识

准备开始

测试您的知识

通过这个互动测验挑战自己,看看你对这个主题的理解程度如何

❓
7
问题
🎯
70%
及格要求
♾️
∞
时间
🔄
∞
尝试次数

📝 说明

  • 仔细阅读每个问题
  • 为每个问题选择最佳答案
  • 您可以随时重新参加测验
  • 您的进度将显示在顶部