Memuat indikator lingkaran berputar

Indikator lingkaran berputar (atau indikator aktivitas) memberi tahu pengguna bahwa suatu proses sedang berlangsung saat durasi pastinya tidak diketahui. Tidak seperti diagram progres, indikator lingkaran berputar bersifat "tidak tentu" dan biasanya menggunakan animasi untuk menandakan aktivitas.

Panduan ini mengimplementasikan indikator lingkaran berputar dengan:

  • Menggunakan elemen <progress> native sebagai fondasi semantik. Dengan menghilangkan atribut value, browser akan memperlakukannya sebagai status progres yang tidak tentu, sehingga memastikan pengumuman yang benar oleh teknologi pendukung.
  • Menata gaya komponen dengan conic-gradient() untuk membuat "jejak" visual dan background-clip: border-area untuk melubangi bagian tengah menjadi cincin.
  • Menganimasikan indikator lingkaran berputar secara efisien menggunakan transformasi CSS dan mematuhi prefers-reduced-motion untuk memastikan pengalaman yang nyaman bagi semua pengguna.

Lihat Diagram progres untuk menangani tugas pasti dengan durasi yang diketahui.

Penerapan

1. Markup

Gunakan elemen <progress> native sebagai sumber kebenaran semantik dan komponen visual. Tanpa atribut value, atribut ini secara implisit tidak ditentukan.

<progress aria-label="Loading" class="loading-spinner"></progress>

2. Gaya

Menyembunyikan UI Native

Untuk menata gaya elemen <progress> sebagai indikator lingkaran berputar, sembunyikan terlebih dahulu gaya browser default untuk status progres yang tidak tentu.

/* Hide native bars */
progress.loading-spinner:indeterminate::-webkit-progress-bar {
  display: none;
  background: none;
}
progress.loading-spinner:indeterminate::-webkit-progress-value {
  display: none;
  background: none;
}
progress.loading-spinner:indeterminate::-moz-progress-bar {
  display: none;
  background: none;
}
progress.loading-spinner:indeterminate::slider-fill {
  display: none;
  background: none;
}

Cincin dan Jejak Spinner

Spinner menggunakan conic-gradient untuk membuat jejak visual.

@property --arc-start {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}
@property --arc-end {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}
/* Use a custom property to conditionally include the dash animation */
@property --progress-dash-animation {
  syntax: "*";
  inherits: false;
  initial-value: , progress-dash 3s ease-in-out infinite;
}

progress.loading-spinner:indeterminate {
  --_from: calc(90deg + var(--arc-start, 0deg));
  --_to: calc(90deg + var(--arc-end, 158deg));
  --size: 40px;
  --thickness: 2px;
  --spinner-color: #3b82f6;
  --track-color: #e2e5e7;
  --spinner-duration: 1.5s;
  --_used-spinner-duration: var(--spinner-duration);
  --spinner-timing: linear;

  position: relative;
  width: var(--size);
  height: var(--size);
  border-radius: 50%;
  appearance: none;

  /* Create the fading trail with dynamic angles */
  background: conic-gradient(
      from var(--_from),
      var(--spinner-color) calc(var(--_to) - var(--_from)),
      transparent 0
    )
    var(--track-color);

  @supports (background-clip: border-area) {
    background-clip: border-area;
    border: var(--thickness) solid transparent;
    background-origin: border-box;
  }

  /* ... fallback for background-clip: border-area ... */

  /* The dash animation is only included if @property is supported */
  animation:
    progress-spin var(--_used-spinner-duration) linear infinite
    var(--progress-dash-animation, );
}

@keyframes progress-spin {
  to {
    rotate: 1turn;
  }
}

@keyframes progress-dash {
  from {
    --arc-start: 0deg;
    --arc-end: 3deg;
  }
  50% {
    --arc-start: 100deg;
    --arc-end: 358deg;
  }
  to {
    --arc-start: 360deg;
    --arc-end: 363deg;
  }
}

Mematuhi Preferensi Gerakan

Pengguna yang sensitif terhadap gerakan mungkin akan merasa bingung dengan elemen yang berputar cepat. Selalu hormati kueri media prefers-reduced-motion. Tetapkan properti --_used-spinner-duration internal untuk mengganti nilai --spinner-duration pengguna.

@media (prefers-reduced-motion: reduce) {
  .loading-spinner {
    /* Slow down the animation significantly rather than stopping it entirely,
       so the user still knows that the process is active. */
    --_used-spinner-duration: 6s;
  }
}

Strategi penggantian

Jika @property didukung, animasi tanda hubung akan otomatis disertakan melalui initial-value properti --progress-dash-animation. Di browser tanpa dukungan @property, pendaftaran properti akan diabaikan, dan animasi akan kembali ke rotasi sederhana. JavaScript tidak diperlukan untuk penggantian ini.

Untuk browser yang belum mendukung background-clip: border-area, gunakan mask-image untuk melubangi bagian tengah.

/* Fallback: use mask-image to create the ring */
@supports not (background-clip: border-area) {
  --clip-boundary: calc(100% - var(--thickness));
  mask-image: radial-gradient(
    farthest-side,
    transparent var(--clip-boundary),
    black var(--clip-boundary)
  );
  border: 0;
}