Skip to content

CSS 动画:打造流畅的关键帧动画

电影不是静止的画面,而是每秒 24 帧图像连续播放形成的流动影像。CSS Animation(动画)运用同样的原理——通过定义关键帧(Keyframes),让元素在多个状态之间流畅过渡。与 Transition(过渡)只能在两个状态间变化不同,Animation 可以创建包含多个步骤的复杂动画,无需用户交互就能自动播放,是打造加载指示器、装饰效果、引导动画的核心工具。

Animation vs Transition:何时使用哪个?

在学习动画之前,先理解它与过渡的区别:

特性Transition(过渡)Animation(动画)
触发方式需要状态变化(如 hover)自动播放,无需触发
控制点只有起点和终点可定义多个关键帧
循环播放不支持支持无限循环
方向控制只能正向或反向支持来回播放、反向等
暂停/恢复不支持支持
适用场景交互反馈(按钮、链接)持续动画(加载、装饰)

简单判断

  • 用户触发的简单效果 → Transition
  • 自动播放或复杂多步骤 → Animation

@keyframes:定义动画序列

@keyframes 规则定义了动画在时间轴上的关键帧,每个关键帧描述元素在该时刻的样式。

基础语法

css
/* 定义一个名为 slidein 的动画 */
@keyframes slidein {
  from {
    transform: translateX(-100%);
    opacity: 0;
  }

  to {
    transform: translateX(0);
    opacity: 1;
  }
}

/* 使用百分比表示更精确的时间点 */
@keyframes fadeInOut {
  0% {
    opacity: 0;
  }

  50% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}
  • from 等同于 0%(动画开始)
  • to 等同于 100%(动画结束)
  • 可以定义任意百分比的关键帧(如 25%、33.33%、75%)

多属性关键帧

每个关键帧可以包含多个 CSS 属性。

css
@keyframes complexAnimation {
  0% {
    transform: translateX(0) rotate(0deg) scale(1);
    background-color: #3498db;
    opacity: 0.5;
  }

  50% {
    transform: translateX(100px) rotate(180deg) scale(1.5);
    background-color: #e74c3c;
    opacity: 1;
  }

  100% {
    transform: translateX(0) rotate(360deg) scale(1);
    background-color: #2ecc71;
    opacity: 0.5;
  }
}

相同样式的关键帧

多个时间点可以共享样式,用逗号分隔。

css
@keyframes pulse {
  0%,
  100% {
    transform: scale(1);
    opacity: 1;
  }

  50% {
    transform: scale(1.1);
    opacity: 0.8;
  }
}

Animation 属性:控制动画播放

定义好关键帧后,使用 animation 属性将其应用到元素上。

animation-name:动画名称

指定要使用的 @keyframes 名称。

css
.box {
  animation-name: slidein;
}

animation-duration:动画时长

动画完成一个周期所需的时间。

css
.box {
  animation-duration: 2s; /* 2秒 */
}

.fast-animation {
  animation-duration: 500ms; /* 0.5秒 */
}

animation-timing-function:缓动函数

控制动画的速度曲线,与 transition-timing-function 相同。

css
.linear {
  animation-timing-function: linear; /* 匀速 */
}

.ease {
  animation-timing-function: ease; /* 慢-快-慢(默认) */
}

.ease-in-out {
  animation-timing-function: ease-in-out; /* 慢速开始和结束 */
}

.custom {
  animation-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);
  /* 弹跳效果 */
}

注意:缓动函数应用于每两个关键帧之间,而不是整个动画。

animation-delay:延迟开始

动画开始前的等待时间。

css
.delayed {
  animation-delay: 1s; /* 1秒后开始 */
}

.negative-delay {
  animation-delay: -1s; /* 负值:跳过前1秒,直接从中间开始 */
}

animation-iteration-count:播放次数

动画重复的次数。

css
.play-once {
  animation-iteration-count: 1; /* 播放1次(默认) */
}

.play-three-times {
  animation-iteration-count: 3; /* 播放3次 */
}

.loop-forever {
  animation-iteration-count: infinite; /* 无限循环 */
}

.fractional {
  animation-iteration-count: 2.5; /* 播放2.5次 */
}

animation-direction:播放方向

控制动画是正向、反向还是来回播放。

css
.normal {
  animation-direction: normal;
  /* 每次都从 0% 到 100%(默认) */
}

.reverse {
  animation-direction: reverse;
  /* 每次都从 100% 到 0% */
}

.alternate {
  animation-direction: alternate;
  /* 奇数次正向(0%→100%),偶数次反向(100%→0%) */
}

.alternate-reverse {
  animation-direction: alternate-reverse;
  /* 奇数次反向,偶数次正向 */
}

实用场景alternate 配合 infinite 可创造来回摆动效果。

animation-fill-mode:填充模式

控制动画开始前和结束后元素的样式。

css
.none-fill {
  animation-fill-mode: none;
  /* 默认:动画结束后恢复原始样式 */
}

.forwards {
  animation-fill-mode: forwards;
  /* 保持最后一帧的样式(100% 或 0% 取决于方向) */
}

.backwards {
  animation-fill-mode: backwards;
  /* 延迟期间应用第一帧样式 */
}

.both {
  animation-fill-mode: both;
  /* 同时应用 forwards 和 backwards */
}

常用forwards 让元素保持动画结束时的状态。

animation-play-state:播放/暂停

控制动画是播放还是暂停。

css
.running {
  animation-play-state: running; /* 播放(默认) */
}

.paused {
  animation-play-state: paused; /* 暂停 */
}

/* 实际应用:悬停时暂停 */
.animation-box {
  animation: spin 2s linear infinite;
}

.animation-box:hover {
  animation-play-state: paused;
}

animation 简写属性

实际开发中更常用简写形式:

css
/* 语法:name duration timing-function delay iteration-count direction fill-mode play-state */
.box {
  animation: slidein 2s ease-in-out 0.5s infinite alternate both running;
}

/* 常用简化版 */
.box {
  animation: slidein 2s infinite;
}

/* 多个动画,用逗号分隔 */
.box {
  animation: slidein 2s, fadeIn 1s 0.5s, rotate 3s infinite;
}

顺序提示:第一个时间值是 duration,第二个是 delay。

实际应用:常见动画效果

加载指示器:旋转圆圈

css
@keyframes spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

.spinner {
  width: 40px;
  height: 40px;
  border: 4px solid #f3f3f3;
  border-top: 4px solid #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

脉冲效果

css
@keyframes pulse {
  0%,
  100% {
    transform: scale(1);
    opacity: 1;
  }

  50% {
    transform: scale(1.05);
    opacity: 0.7;
  }
}

.pulse-button {
  animation: pulse 2s ease-in-out infinite;
}

弹跳动画

css
@keyframes bounce {
  0%,
  20%,
  50%,
  80%,
  100% {
    transform: translateY(0);
  }

  40% {
    transform: translateY(-30px);
  }

  60% {
    transform: translateY(-15px);
  }
}

.bounce-element {
  animation: bounce 2s infinite;
}

淡入淡出

css
@keyframes fadeInOut {
  0%,
  100% {
    opacity: 0;
  }

  50% {
    opacity: 1;
  }
}

.fade-text {
  animation: fadeInOut 3s infinite;
}

滑入效果

css
@keyframes slideInLeft {
  from {
    transform: translateX(-100%);
    opacity: 0;
  }

  to {
    transform: translateX(0);
    opacity: 1;
  }
}

.slide-in {
  animation: slideInLeft 0.5s ease-out forwards;
}

摇晃效果

css
@keyframes shake {
  0%,
  100% {
    transform: translateX(0);
  }

  10%,
  30%,
  50%,
  70%,
  90% {
    transform: translateX(-10px);
  }

  20%,
  40%,
  60%,
  80% {
    transform: translateX(10px);
  }
}

.shake-on-error {
  animation: shake 0.5s;
}

打字机效果

css
@keyframes typing {
  from {
    width: 0;
  }
  to {
    width: 100%;
  }
}

@keyframes blink {
  50% {
    border-color: transparent;
  }
}

.typewriter {
  width: 0;
  overflow: hidden;
  white-space: nowrap;
  border-right: 3px solid #333;
  animation: typing 3.5s steps(40) 1s forwards, blink 0.75s step-end infinite;
}

实战案例:复杂加载动画

创建一个多点跳动的加载指示器。

html
<div class="loader">
  <div class="dot"></div>
  <div class="dot"></div>
  <div class="dot"></div>
</div>
css
@keyframes bounce {
  0%,
  80%,
  100% {
    transform: scale(0);
    opacity: 0.5;
  }

  40% {
    transform: scale(1);
    opacity: 1;
  }
}

.loader {
  display: flex;
  gap: 10px;
  justify-content: center;
  align-items: center;
  height: 100px;
}

.dot {
  width: 15px;
  height: 15px;
  background: #3498db;
  border-radius: 50%;
  animation: bounce 1.4s infinite ease-in-out both;
}

/* 错开动画时间 */
.dot:nth-child(1) {
  animation-delay: -0.32s;
}

.dot:nth-child(2) {
  animation-delay: -0.16s;
}

.dot:nth-child(3) {
  animation-delay: 0s;
}

实战案例:进度条动画

一个填充的进度条。

html
<div class="progress-container">
  <div class="progress-bar"></div>
</div>
css
@keyframes fillProgress {
  from {
    width: 0%;
  }
  to {
    width: 100%;
  }
}

.progress-container {
  width: 100%;
  height: 30px;
  background: #ecf0f1;
  border-radius: 15px;
  overflow: hidden;
}

.progress-bar {
  height: 100%;
  background: linear-gradient(90deg, #3498db, #2ecc71);
  border-radius: 15px;
  animation: fillProgress 3s ease-out forwards;
}

实战案例:浮动图标

创建一个上下漂浮的图标。

css
@keyframes float {
  0%,
  100% {
    transform: translateY(0);
  }

  50% {
    transform: translateY(-20px);
  }
}

@keyframes floatShadow {
  0%,
  100% {
    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
  }

  50% {
    box-shadow: 0 15px 25px rgba(0, 0, 0, 0.1);
  }
}

.floating-icon {
  width: 60px;
  height: 60px;
  background: #3498db;
  border-radius: 50%;
  animation: float 3s ease-in-out infinite, floatShadow 3s ease-in-out infinite;
}

实战案例:卡片翻转动画

自动翻转的卡片。

html
<div class="auto-flip-card">
  <div class="card-inner">
    <div class="card-front">Front</div>
    <div class="card-back">Back</div>
  </div>
</div>
css
@keyframes flip {
  0%,
  45% {
    transform: rotateY(0deg);
  }

  50%,
  95% {
    transform: rotateY(180deg);
  }

  100% {
    transform: rotateY(360deg);
  }
}

.auto-flip-card {
  width: 200px;
  height: 300px;
  perspective: 1000px;
}

.card-inner {
  width: 100%;
  height: 100%;
  position: relative;
  transform-style: preserve-3d;
  animation: flip 6s infinite;
}

.card-front,
.card-back {
  position: absolute;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 24px;
  font-weight: bold;
  border-radius: 12px;
}

.card-front {
  background: linear-gradient(135deg, #667eea, #764ba2);
  color: white;
}

.card-back {
  background: linear-gradient(135deg, #f093fb, #f5576c);
  color: white;
  transform: rotateY(180deg);
}

组合多个动画

一个元素可以同时运行多个动画。

css
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

@keyframes colorChange {
  0% {
    background: #3498db;
  }
  50% {
    background: #e74c3c;
  }
  100% {
    background: #2ecc71;
  }
}

@keyframes scale {
  0%,
  100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.2);
  }
}

.multi-animation {
  animation: spin 3s linear infinite, colorChange 6s ease-in-out infinite,
    scale 2s ease-in-out infinite;
}

注意:如果多个动画修改同一属性(如 transform),后面的会覆盖前面的。需要将它们合并到一个 @keyframes 中。

JavaScript 控制动画

通过 JavaScript 可以动态控制动画。

javascript
const element = document.querySelector(".box");

// 添加动画
element.style.animation = "slidein 2s ease-out forwards";

// 移除动画
element.style.animation = "none";

// 暂停动画
element.style.animationPlayState = "paused";

// 恢复动画
element.style.animationPlayState = "running";

// 监听动画事件
element.addEventListener("animationstart", () => {
  console.log("Animation started");
});

element.addEventListener("animationiteration", () => {
  console.log("Animation iteration");
});

element.addEventListener("animationend", () => {
  console.log("Animation ended");
});

性能优化与最佳实践

优先使用 transform 和 opacity

css
/* ❌ 低性能:触发重排/重绘 */
@keyframes badAnimation {
  from {
    width: 100px;
    height: 100px;
    left: 0;
  }
  to {
    width: 200px;
    height: 200px;
    left: 100px;
  }
}

/* ✅ 高性能:只触发合成 */
@keyframes goodAnimation {
  from {
    transform: scale(1) translateX(0);
    opacity: 1;
  }
  to {
    transform: scale(2) translateX(100px);
    opacity: 0.5;
  }
}

减少动画的元素数量

css
/* ⚠️ 数百个元素同时动画会卡顿 */
.many-items {
  animation: complex 2s infinite;
}

/* ✅ 只在必要的元素上使用动画 */
.featured-item {
  animation: highlight 2s infinite;
}

使用 will-change

css
.heavy-animation {
  will-change: transform, opacity;
  animation: complexMove 2s infinite;
}

控制帧率:使用 steps()

对于精灵图动画,使用 steps() 代替平滑过渡。

css
@keyframes spriteAnimation {
  to {
    background-position: -480px 0; /* 假设有8帧,每帧60px */
  }
}

.sprite {
  width: 60px;
  height: 60px;
  background: url("sprite.png") no-repeat;
  animation: spriteAnimation 0.8s steps(8) infinite;
  /* 8步,每帧显示0.1秒 */
}

常见问题与解决方案

问题 1:动画在页面加载时立即播放

有时希望用户看到某个状态后再开始动画。

css
/* 使用 animation-delay */
.delayed-start {
  animation: fadeIn 1s 2s forwards;
  /* 延迟2秒后开始 */
}

/* 或者用 JavaScript 控制 */
javascript
setTimeout(() => {
  document.querySelector(".box").classList.add("animated");
}, 2000);

问题 2:动画结束后闪烁

使用 animation-fill-mode: forwards 保持最后一帧。

css
.fade-out {
  animation: fadeOut 1s forwards; /* 不会跳回初始状态 */
}

问题 3:无限动画占用资源

在不可见时暂停动画。

javascript
document.addEventListener("visibilitychange", () => {
  const animations = document.querySelectorAll(".animated");
  animations.forEach((el) => {
    el.style.animationPlayState = document.hidden ? "paused" : "running";
  });
});

问题 4:动画在移动端不流畅

css
/* 添加硬件加速 */
.smooth-animation {
  transform: translate3d(0, 0, 0); /* 触发GPU加速 */
  will-change: transform;
  animation: move 2s infinite;
}

可访问性考虑

某些用户可能对动画敏感,应尊重系统设置。

css
/* 检测用户是否偏好减少动画 */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* 或者只关闭特定动画 */
@media (prefers-reduced-motion: reduce) {
  .decorative-animation {
    animation: none;
  }

  .functional-animation {
    animation-duration: 0.3s; /* 保留但缩短 */
  }
}

总结

CSS Animation 是创造动态、引人注目视觉体验的强大工具:

核心概念

  • @keyframes:定义动画序列的关键帧
  • animation-name:指定要使用的动画
  • animation-duration:动画时长
  • animation-iteration-count:播放次数(可无限循环)
  • animation-direction:播放方向
  • animation-fill-mode:动画前后的状态

最佳实践

  1. 性能优先:使用 transformopacity
  2. 适度使用:不是所有元素都需要动画
  3. 考虑可访问性:尊重 prefers-reduced-motion
  4. 语义明确:动画名称应描述其效果
  5. 测试性能:在低端设备上验证流畅度

何时使用 Animation

  • 加载指示器(旋转、脉冲、进度条)
  • 装饰性动画(背景图案、漂浮元素)
  • 引导动画(箭头指示、高亮提示)
  • 品牌动效(Logo 动画、标志性效果)

CSS 动画让静态网页活了起来,但记住——动画是服务于用户体验的工具,而不是目的本身。最好的动画是那些增强理解、提供反馈、引导注意力的动画,而不是那些分散注意力、炫耀技巧的动画。优雅的动画应该像呼吸一样自然,用户会感受到它的存在,但不会被它打扰。