Анимация загрузки (spinner)

CSS-спиннер на чистом CSS без изображений и JS — кружок с вращающимся бортиком через border + animation.

Задача

Нужно показать индикатор загрузки во время асинхронной операции. Спиннер должен быть лёгким, не требовать JS и легко встраиваться в кнопки и оверлеи.

Решение

Базовый спиннер:

<div class="spinner" role="status" aria-label="Загрузка..."></div>
.spinner {
  width: 36px;
  height: 36px;
  border: 3px solid #e2e8f0;      /* светлая подложка */
  border-top-color: #3b82f6;      /* цветной «бегущий» участок */
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

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

Спиннер внутри кнопки:

<button class="btn btn--loading" disabled>
  <span class="btn__spinner"></span>
  Сохранение...
</button>
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 20px;
  background: #3b82f6;
  color: #fff;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-size: 0.9rem;
}

.btn--loading { opacity: 0.7; cursor: not-allowed; }

.btn__spinner {
  width: 16px;
  height: 16px;
  border: 2px solid rgba(255,255,255,0.4);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
  flex-shrink: 0;
}

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

Fullscreen overlay:

<div class="loading-overlay" id="overlay">
  <div class="spinner spinner--lg"></div>
</div>
.loading-overlay {
  position: fixed;
  inset: 0;
  background: rgba(255,255,255,0.8);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 999;
}

.spinner--lg { width: 56px; height: 56px; border-width: 4px; }
const overlay = document.getElementById('overlay');

async function loadData() {
  overlay.hidden = false;
  try {
    await fetchData;
  } finally {
    overlay.hidden = true;
  }
}

Ключевые моменты

  • border + border-top-color — классический трюк: три стороны серые, одна цветная; вращение даёт эффект спиннера.
  • animation: spin 0.8s linear infinitelinear важен, иначе вращение не будет равномерным.
  • role="status" + aria-label — доступность: screen reader объявит «загрузка».
  • flex-shrink: 0 на спиннере в кнопке — предотвращает сжатие при малой ширине кнопки.

Варианты

  • Dots spinner (три точки): @keyframes bounce на трёх <span> с разной animation-delay.
  • SVG spinner: <circle stroke-dasharray> + анимация stroke-dashoffset — более гибкий.
  • Skeleton — предпочтительнее спиннера для контентных областей; см. Скелетон загрузки.

Связанные рецепты / темы