Diagram progres

Diagram progres (atau status progres melingkar) memberikan masukan visual tentang status tugas. Tidak seperti status progres linear, bentuk melingkarnya ideal untuk dasbor, komponen kartu, atau di mana pun ruang terbatas.

Panduan ini mengimplementasikan diagram progres dengan:

  • Menggunakan elemen <progress> native sebagai fondasi semantik untuk memastikan komponen dapat diakses oleh pembaca layar dan pengguna keyboard secara langsung.
  • Menata gaya komponen dengan conic-gradient() dan mask-image, sehingga memungkinkan cincin yang sepenuhnya responsif dan dapat diberi tema tanpa kerumitan manipulasi jalur SVG.
  • Memanfaatkan Properti Kustom CSS dan @property untuk memungkinkan transisi pengisian progres yang lancar dan dipercepat GPU.

Pendekatan ini lebih disukai daripada solusi khusus SVG karena menggunakan elemen <progress> semantik, bukan ARIA, dan lebih mudah diintegrasikan dengan tata letak, sistem desain, dan tipografi yang ada.

Lihat Indikator lingkaran berputar untuk menangani status pemuatan yang tidak tentu.

Penerapan

1. Markup

Gunakan wrapper untuk menampung cincin visual dan konten tengah opsional. Elemen <progress> tetap menjadi sumber kebenaran semantik. Gunakan class utilitas untuk menyembunyikan status progres native secara visual sekaligus membuatnya tetap dapat diakses.

<div class="ring-wrapper">
  <progress value="75" max="100" aria-label="Task progress" class="progress-ring"></progress>
  <!-- Optional: Content to display in the center -->
  <div class="ring-content">
    75%
  </div>
</div>

2. Gaya

Menyembunyikan UI Native

Untuk menata gaya elemen <progress> sebagai diagram progres, sembunyikan terlebih dahulu gaya browser default untuk status progres.

/* 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;
}

Container dan Ring

Wrapper menyediakan konteks pemosisian. Elemen <progress> menangani gradien visual.

.progress-ring-wrapper {
  position: relative;
  display: grid;
  place-items: center;
}

progress.progress-ring {
  --size: 150px;
  --thickness: 16px;
  --track-color: #f1f5f9;
  --fill-color: #3b82f6;
  --value: attr(value type(<number>));
  width: var(--size);
  height: var(--size);

  transition: --value 0.4s cubic-bezier(0.4, 0, 0.2, 1);
  border-radius: 50%;

  background: conic-gradient(
    var(--fill-color) calc(var(--value) * 1%),
    var(--track-color) 0
  );

  /* MANDATORY: Clip the background to the border-area. */
  background-clip: border-area;
  border: var(--thickness) solid transparent;
  background-origin: border-box;
}

.progress-ring-content {
  /* Positioned in the center of the wrapper */
  position: absolute;
}

Anda juga dapat menggunakan radial-gradient untuk membuat ujung bulat.

Mengaktifkan transisi yang lancar dengan @property

Untuk menganimasikan diagram progres dengan lancar saat nilai berubah, daftarkan --value sebagai properti kustom numerik.

Pengguna yang sensitif terhadap gerakan mungkin merasa bingung dengan transisi antar-nilai. Hormati kueri media prefers-reduced-motion dengan memiliki durasi 0 detik (langsung) secara default, dan menetapkan waktu yang lebih lama untuk pengguna tanpa preferensi.

@property --value {
  syntax: '<number>';
  inherits: true;
  initial-value: 0;
}

progress.progress-ring {
  transition: --value 0s ease-in-out;
  @media (prefers-reduced-motion: no-preference) {
    transition-duration: 0.4s;
  }
}

3. Info Terbaru Progres

Perbarui atribut value pada elemen <progress> dan konten teks .progress-ring-content (jika menampilkan persentase) setiap kali nilainya berubah.

4. Status Keberhasilan Opsional

Anda dapat menggunakan pemilih atribut CSS untuk otomatis memperbarui tampilan cincin (misalnya, mengubah warna menjadi hijau) saat tugas selesai.

/* Change the fill color to green when the progress reaches 100% */
progress.progress-ring[value="100"] {
  --fill-color: #10b981;
}

Strategi penggantian

Jangan tambahkan nilai penggantian di dalam elemen <progress>. Atribut ini tidak digunakan oleh teknologi pendukung dan diabaikan oleh semua browser modern.

Penggantian animasi

Jika @property tidak didukung, cincin akan langsung melompat ke nilai baru, bukan bertransisi dengan lancar. Dalam sebagian besar kasus, hal ini tidak menjadi masalah dan tidak merusak fungsi apa pun.

Jika transisi benar-benar diperlukan, Anda dapat memeriksa dukungan @property dan menggunakan loop requestAnimationFrame() untuk menginterpolasi --value di browser lama.

Untuk browser yang belum mendukung background-clip: border-area, gunakan mask-image sebagai pengganti untuk mengosongkan bagian tengah elemen <progress>.

@supports not (background-clip: border-area) {
  mask-image: radial-gradient(
    transparent calc(50% - var(--thickness)),
    black calc(50% - var(--thickness) + 0.5px)
  );
  border: 0;
}

Untuk browser yang tidak mendukung fungsi CSS attr() untuk properti apa pun, gunakan MutationObserver untuk otomatis menyinkronkan atribut value ke properti kustom --value.

if (!CSS.supports("width: attr(value type(<number>))")) {
  const observer = new MutationObserver(() => {
    progress.style.setProperty("--value", progress.getAttribute("value"));
  });
  observer.observe(progress, {
    attributes: true,
    attributeFilter: ["value"],
  });
}