⏳ 加载动画

使用 @keyframes 和 animation-play-state 创建多种加载指示器

交互演示

经典旋转

使用 rotate 实现的经典加载器

脉冲缩放

使用 scale 实现的脉冲效果

渐变点

使用 opacity 实现的渐变效果

弹跳球

组合 scale 和 translateY 的弹跳效果

渐进点

依次显示1个、2个、3个点的循环效果

💡 提示:点击单个按钮控制对应动画,或使用"暂停所有"控制全部

实现步骤

步骤 1:HTML 结构

创建不同类型的加载器容器:


<div class="spinner spinner-classic"></div>


<div class="spinner spinner-pulse"></div>


<div class="spinner spinner-dots">
  <span></span>
  <span></span>
  <span></span>
</div>


<div class="spinner spinner-bounce">
  <span></span>
  <span></span>
  <span></span>
</div>


<div class="spinner spinner-progressive">
  <span></span>
  <span></span>
  <span></span>
</div>

步骤 2:定义 @keyframes 动画

为每种加载器创建关键帧动画:

/* 经典旋转动画 */
@keyframes spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

/* 脉冲缩放动画 */
@keyframes pulse {
  0%, 100% {
    transform: scale(1);
    opacity: 1;
  }
  50% {
    transform: scale(1.5);
    opacity: 0.5;
  }
}

/* 渐变动画 */
@keyframes fade {
  0%, 100% {
    opacity: 0.2;
  }
  50% {
    opacity: 1;
  }
}

/* 弹跳动画 */
@keyframes bounce {
  0%, 100% {
    transform: translateY(0) scale(1);
  }
  50% {
    transform: translateY(-20px) scale(0.8);
  }
}

/* 渐进点动画 - 第1个点 */
@keyframes progressiveDot1 {
  0% {
    opacity: 1;
    transform: scale(1);
  }
  75% {
    opacity: 1;
  }
  85% {
    opacity: 0;
    transform: scale(0);
  }
}

/* 渐进点动画 - 第2个点 */
@keyframes progressiveDot2 {
  0%, 25% {
    opacity: 0;
    transform: scale(0);
  }
  30% {
    opacity: 1;
    transform: scale(1);
  }
  75% {
    opacity: 1;
  }
  85% {
    opacity: 0;
  }
}

/* 渐进点动画 - 第3个点 */
@keyframes progressiveDot3 {
  0%, 50% {
    opacity: 0;
    transform: scale(0);
  }
  55% {
    opacity: 1;
    transform: scale(1);
  }
  75% {
    opacity: 1;
  }
  85% {
    opacity: 0;
  }
}

步骤 3:应用动画到元素

使用 animation 属性应用动画:

/* 经典旋转 */
.spinner-classic {
  width: 50px;
  height: 50px;
  border: 4px solid var(--gray-200);
  border-top-color: var(--primary);
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

/* 脉冲缩放 */
.spinner-pulse {
  width: 50px;
  height: 50px;
  background: var(--primary);
  border-radius: 50%;
  animation: pulse 1.5s ease-in-out infinite;
}

/* 渐变点 */
.spinner-dots span {
  width: 12px;
  height: 12px;
  background: var(--primary);
  border-radius: 50%;
  animation: fade 1.4s ease-in-out infinite;
}

.spinner-dots span:nth-child(2) {
  animation-delay: 0.2s;
}

.spinner-dots span:nth-child(3) {
  animation-delay: 0.4s;
}

/* 渐进点 - 依次显示1个、2个、3个点 */
.spinner-progressive span {
  width: 12px;
  height: 12px;
  background: var(--primary);
  border-radius: 50%;
  opacity: 0;
  transform: scale(0);
}

.spinner-progressive span:nth-child(1) {
  animation: progressiveDot1 2s ease-in-out infinite;
}

.spinner-progressive span:nth-child(2) {
  animation: progressiveDot2 2s ease-in-out infinite;
}

.spinner-progressive span:nth-child(3) {
  animation: progressiveDot3 2s ease-in-out infinite;
}

步骤 4:使用 animation-play-state 控制播放

通过 JavaScript 切换 animation-play-state:

/* CSS 暂停状态 */
.spinner.paused {
  animation-play-state: paused;
}

.spinner.paused * {
  animation-play-state: paused;
}

// JavaScript 控制
const spinner = document.querySelector('.spinner-classic');
const button = document.querySelector('[data-toggle]');

button.addEventListener('click', () => {
  spinner.classList.toggle('paused');

  if (spinner.classList.contains('paused')) {
    button.textContent = '▶️ 播放';
  } else {
    button.textContent = '⏸️ 暂停';
  }
});

关键要点

  • @keyframes 定义:使用 @keyframes 定义可复用的动画序列,支持百分比或 from/to 关键字
  • animation 属性:简写属性包含 name、duration、timing-function、iteration-count 等
  • infinite 循环:使用 animation-iteration-count: infinite 创建无限循环动画
  • animation-play-state:通过 paused/running 值控制动画的播放和暂停
  • animation-delay:使用延迟创建交错效果,让多个元素依次动画
  • 性能优化:优先使用 transform 和 opacity 属性,它们由 GPU 加速
  • 组合动画:可以在一个 @keyframes 中组合多个属性变化(rotate + scale + opacity)