Membuat komponen panel pemuatan

Ringkasan dasar tentang cara mem-build panel pemuatan yang adaptif dan mudah diakses warna dengan elemen <progress>.

Dalam postingan ini, saya ingin berbagi pemikiran tentang cara mem-build panel pemuatan yang adaptif dan mudah diakses menggunakan elemen <progress>. Coba demonya dan lihat sumbernya.

Terang dan gelap, tidak pasti, meningkat, dan penyelesaian didemosikan di Chrome.

Jika Anda lebih suka video, berikut versi YouTube postingan ini:

Ringkasan

Elemen <progress> memberikan masukan visual dan audio kepada pengguna tentang penyelesaian. Masukan visual ini berguna untuk skenario seperti: progres dalam formulir, menampilkan informasi yang didownload atau diupload, atau bahkan menunjukkan bahwa jumlah progres tidak diketahui, tetapi pekerjaan masih aktif.

GUI Challenge ini menggunakan elemen <progress> HTML yang ada untuk mempermudah aksesibilitas. Warna dan tata letak mendorong batas penyesuaian untuk elemen bawaan, untuk memodernisasi komponen dan membuatnya lebih cocok dalam sistem desain.

Tab terang dan gelap di setiap browser yang memberikan
    ringkasan ikon adaptif dari atas ke bawah: 
    Safari, Firefox, Chrome.
Demo ditampilkan di Firefox, Safari, iOS Safari, Chrome, dan Android Chrome dalam skema terang dan gelap.

Markup

Saya memilih untuk menggabungkan elemen <progress> dalam <label> sehingga saya dapat melewati atribut hubungan eksplisit demi hubungan implisit. Saya juga telah memberi label pada elemen induk yang terpengaruh oleh status pemuatan, sehingga teknologi pembaca layar dapat menyampaikan kembali informasi tersebut ke pengguna.

<progress></progress>

Jika tidak ada value, progres elemen tidak dapat ditentukan. Atribut max ditetapkan secara default ke 1, sehingga progresnya antara 0 dan 1. Misalnya, menetapkan max ke 100 akan menetapkan rentang ke 0-100. Saya memilih untuk tetap dalam batas 0 dan 1, menerjemahkan nilai progres menjadi 0,5 atau 50%.

Progres penggabungan label

Dalam hubungan implisit, elemen progres digabungkan oleh label seperti ini:

<label>Loading progress<progress></progress></label>

Dalam demo saya, saya memilih untuk menyertakan label hanya untuk pembaca layar. Hal ini dilakukan dengan menggabungkan teks label dalam <span> dan menerapkan beberapa gaya ke teks tersebut sehingga dapat berada di luar layar secara efektif:

<label>
  <span class="sr-only">Loading progress</span>
  <progress></progress>
</label>

Dengan CSS pendamping berikut dari WebAIM:

.sr-only {
  clip: rect(1px, 1px, 1px, 1px);
  clip-path: inset(50%);
  height: 1px;
  width: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
}

Screenshot devtools menampilkan elemen khusus layar.

Area yang terpengaruh oleh progres pemuatan

Jika Anda memiliki penglihatan yang sehat, akan mudah mengaitkan indikator progres dengan elemen dan area halaman terkait, tetapi untuk pengguna dengan gangguan penglihatan, hal ini tidak terlalu jelas. Tingkatkan hal ini dengan menetapkan atribut aria-busy ke elemen teratas yang akan berubah saat pemuatan selesai. Selanjutnya, tunjukkan hubungan antara progres dan zona pemuatan dengan aria-describedby.

<main id="loading-zone" aria-busy="true">
  …
  <progress aria-describedby="loading-zone"></progress>
</main>

Dari JavaScript, alihkan aria-busy ke true di awal tugas, dan ke false setelah selesai.

Penambahan atribut Aria

Meskipun peran implisit elemen <progress> adalah progressbar, saya membuatnya eksplisit untuk browser yang tidak memiliki peran implisit tersebut. Saya juga telah menambahkan atribut indeterminate untuk secara eksplisit menempatkan elemen ke dalam status tidak diketahui, yang lebih jelas daripada mengamati elemen tidak memiliki value yang ditetapkan.

<label>
  Loading 
  <progress 
    indeterminate 
    role="progressbar" 
    aria-describedby="loading-zone"
    tabindex="-1"
  >unknown</progress>
</label>

Gunakan tabindex="-1" untuk membuat elemen progres dapat difokuskan dari JavaScript. Hal ini penting untuk teknologi pembaca layar, karena memberikan fokus progres seiring perubahan progres, akan memberi tahu pengguna seberapa jauh progres yang diperbarui telah tercapai.

Gaya

Elemen progres agak rumit untuk penataan gaya. Elemen HTML bawaan memiliki bagian khusus yang tersembunyi yang mungkin sulit dipilih dan sering kali hanya menawarkan kumpulan properti terbatas untuk ditetapkan.

Tata Letak

Gaya tata letak dimaksudkan untuk memungkinkan fleksibilitas dalam ukuran dan posisi label elemen progres. Status penyelesaian khusus ditambahkan yang dapat menjadi tanda visual tambahan yang berguna, tetapi tidak wajib.

Tata Letak <progress>

Lebar elemen progres tidak disentuh sehingga dapat menyusut dan bertambah sesuai ruang yang dibutuhkan dalam desain. Gaya bawaan dihilangkan dengan menetapkan appearance dan border ke none. Hal ini dilakukan agar elemen dapat dinormalkan di seluruh browser, karena setiap browser memiliki gayanya sendiri untuk elemennya.

progress {
  --_track-size: min(10px, 1ex);
  --_radius: 1e3px;

  /*  reset  */
  appearance: none;
  border: none;

  position: relative;
  height: var(--_track-size);
  border-radius: var(--_radius);
  overflow: hidden;
}

Nilai 1e3px untuk _radius menggunakan notasi angka ilmiah untuk mengekspresikan angka besar sehingga border-radius selalu dibulatkan. Class ini setara dengan 1000px. Saya suka menggunakan ini karena tujuan saya adalah menggunakan nilai yang cukup besar sehingga saya dapat menyetelnya dan melupakannya (dan lebih pendek untuk menulisnya daripada 1000px). Selain itu, mudah untuk membuatnya lebih besar lagi jika diperlukan: cukup ubah 3 menjadi 4, lalu 1e4px setara dengan 10000px.

overflow: hidden digunakan dan menjadi gaya yang sering diperdebatkan. Hal ini mempermudah beberapa hal, seperti tidak perlu meneruskan nilai border-radius ke jalur dan melacak elemen isian; tetapi itu juga berarti tidak ada turunan progres yang dapat berada di luar elemen. Iterasi lain pada elemen progres kustom ini dapat dilakukan tanpa overflow: hidden dan dapat membuka beberapa peluang untuk animasi atau status penyelesaian yang lebih baik.

Proses selesai

Pemilih CSS melakukan pekerjaan yang sulit di sini dengan membandingkan nilai maksimum dengan nilai, dan jika cocok, maka progresnya selesai. Setelah selesai, elemen semu akan dihasilkan dan ditambahkan ke akhir elemen progres, sehingga memberikan isyarat visual tambahan yang bagus untuk penyelesaiannya.

progress:not([max])[value="1"]::before,
progress[max="100"][value="100"]::before {
  content: "✓";
  
  position: absolute;
  inset-block: 0;
  inset-inline: auto 0;
  display: flex;
  align-items: center;
  padding-inline-end: max(calc(var(--_track-size) / 4), 3px);

  color: white;
  font-size: calc(var(--_track-size) / 1.25);
}

Screenshot panel pemuatan saat mencapai 100% dan menampilkan tanda centang di bagian akhir.

Warna

Browser memiliki warnanya sendiri untuk elemen progres, dan dapat beradaptasi dengan terang dan gelap hanya dengan satu properti CSS. Hal ini dapat dibangun dengan beberapa pemilih khusus browser khusus.

Gaya browser terang dan gelap

Untuk mengatur situs Anda menggunakan elemen <progress> adaptif gelap dan terang, Anda hanya diperlukan color-scheme.

progress {
  color-scheme: light dark;
}

Warna yang terisi progres properti tunggal

Untuk menambahkan tint pada elemen <progress>, gunakan accent-color.

progress {
  accent-color: rebeccapurple;
}

Perhatikan warna latar belakang trek berubah dari terang menjadi gelap, bergantung pada accent-color. Browser memastikan kontras yang tepat: cukup rapi.

Warna terang dan gelap yang sepenuhnya kustom

Tetapkan dua properti khusus pada elemen <progress>, satu untuk warna trek dan satu lagi untuk warna progres trek. Di dalam kueri media prefers-color-scheme, berikan nilai warna baru untuk jalur dan lacak progres.

progress {
  --_track: hsl(228 100% 90%);
  --_progress: hsl(228 100% 50%);
}

@media (prefers-color-scheme: dark) {
  progress {
    --_track: hsl(228 20% 30%);
    --_progress: hsl(228 100% 75%);
  }
}

Gaya fokus

Sebelumnya, kita memberikan indeks tab negatif ke elemen sehingga dapat difokuskan secara terprogram. Gunakan :focus-visible untuk menyesuaikan fokus agar dapat memilih gaya cincin fokus yang lebih cerdas. Dengan tindakan ini, klik mouse dan fokus tidak akan menampilkan lingkaran fokus, tetapi klik keyboard. Video YouTube membahas hal ini secara lebih mendalam dan layak untuk ditinjau.

progress:focus-visible {
  outline-color: var(--_progress);
  outline-offset: 5px;
}

Screenshot panel pemuatan dengan lingkaran fokus di sekelilingnya. Warna semua cocok.

Gaya kustom di seluruh browser

Sesuaikan gaya dengan memilih bagian-bagian elemen <progress> yang diekspos oleh setiap browser. Penggunaan elemen progres adalah satu tag, tetapi terbuat dari beberapa elemen turunan yang ditampilkan melalui pemilih pseudo CSS. Chrome DevTools akan menampilkan elemen ini kepada Anda jika Anda mengaktifkan setelan:

  1. Klik kanan halaman dan pilih Periksa Elemen untuk menampilkan DevTools.
  2. Klik roda gigi Settings di sudut kanan atas jendela DevTools.
  3. Di bagian judul Elements, cari dan aktifkan kotak centang Tampilkan shadow DOM agen pengguna.

Screenshot tempat di DevTools untuk mengaktifkan eksposur shadow DOM agen pengguna.

Gaya Safari dan Chromium

Browser berbasis WebKit seperti Safari dan Chromium mengekspos ::-webkit-progress-bar dan ::-webkit-progress-value, yang memungkinkan penggunaan subset CSS. Untuk saat ini, tetapkan background-color menggunakan properti kustom yang dibuat sebelumnya, yang beradaptasi dengan terang dan gelap.

/*  Safari/Chromium  */
progress[value]::-webkit-progress-bar {
  background-color: var(--_track);
}

progress[value]::-webkit-progress-value {
  background-color: var(--_progress);
}

Screenshot yang menunjukkan elemen dalam elemen progres.

Gaya Firefox

Firefox hanya mengekspos pemilih pseudo ::-moz-progress-bar pada elemen <progress>. Artinya, kita tidak dapat menambahkan tint pada trek secara langsung.

/*  Firefox  */
progress[value]::-moz-progress-bar {
  background-color: var(--_progress);
}

Screenshot Firefox dan tempat menemukan bagian elemen progres.

Screenshot Sudut Debug tempat Safari, iOS Safari, 
  Firefox, Chrome, dan Chrome di Android menampilkan panel pemuatan yang berfungsi.

Perhatikan bahwa Firefox memiliki warna track yang ditetapkan dari accent-color, sedangkan iOS Safari memiliki trek berwarna biru muda. Begitu juga dalam mode gelap: Firefox memiliki jalur gelap, namun bukan warna khusus yang telah kita tetapkan, dan berfungsi di browser berbasis Webkit.

Animasi

Saat bekerja dengan pemilih pseudo bawaan browser, pemilih sering kali memiliki sekumpulan properti CSS yang diizinkan dalam jumlah terbatas.

Menganimasikan pengisian trek

Menambahkan transisi ke inline-size elemen progres berfungsi untuk Chromium, tetapi tidak untuk Safari. Firefox juga tidak menggunakan properti transisi pada ::-moz-progress-bar.

/*  Chromium Only 😢  */
progress[value]::-webkit-progress-value {
  background-color: var(--_progress);
  transition: inline-size .25s ease-out;
}

Menganimasikan status :indeterminate

Di sini saya menjadi sedikit lebih kreatif sehingga saya bisa memberikan animasi. Elemen pseudo untuk Chromium dibuat dan gradien diterapkan, yang dianimasikan bolak-balik, untuk ketiga browser.

Properti khusus

Properti khusus sangat bagus untuk banyak hal, tetapi salah satu favorit saya adalah memberikan nama pada nilai CSS yang terlihat luar biasa. Mengikuti adalah linear-gradient yang cukup rumit, tetapi dengan nama yang bagus. Tujuan dan kasus penggunaannya dapat dipahami dengan jelas.

progress {
  --_indeterminate-track: linear-gradient(to right,
    var(--_track) 45%,
    var(--_progress) 0%,
    var(--_progress) 55%,
    var(--_track) 0%
  );
  --_indeterminate-track-size: 225% 100%;
  --_indeterminate-track-animation: progress-loading 2s infinite ease;
}

Properti khusus juga akan membantu kode tetap KERING karena sekali lagi, kita tidak dapat mengelompokkan pemilih khusus browser ini bersama-sama.

Keyframe

Tujuannya adalah animasi tanpa batas yang berjalan bolak-balik. Keyframe awal dan akhir akan ditetapkan di CSS. Hanya satu keyframe yang diperlukan, yaitu keyframe tengah di 50%, untuk membuat animasi yang kembali ke tempat awal dimulai, berulang kali.

@keyframes progress-loading {
  50% {
    background-position: left; 
  }
}

Menargetkan setiap browser

Tidak semua browser mengizinkan pembuatan elemen pseudo pada elemen <progress> itu sendiri atau memungkinkan animasi status progres. Lebih banyak browser yang mendukung animasi trek daripada elemen pseudo, jadi saya mengupgrade dari elemen pseudo sebagai dasar dan menjadi batang animasi.

Elemen semu Chromium

Chromium mengizinkan elemen pseudo: ::after digunakan dengan posisi untuk menutupi elemen. Properti khusus yang tidak tentu digunakan, dan animasi bolak-balik berfungsi dengan sangat baik.

progress:indeterminate::after {
  content: "";
  inset: 0;
  position: absolute;
  background: var(--_indeterminate-track);
  background-size: var(--_indeterminate-track-size);
  background-position: right; 
  animation: var(--_indeterminate-track-animation);
}
Status progres Safari

Untuk Safari, properti khusus dan animasi diterapkan ke status progres elemen semu:

progress:indeterminate::-webkit-progress-bar {
  background: var(--_indeterminate-track);
  background-size: var(--_indeterminate-track-size);
  background-position: right; 
  animation: var(--_indeterminate-track-animation);
}
Bilah kemajuan Firefox

Untuk Firefox, properti khusus dan animasi juga diterapkan di status progres elemen semu:

progress:indeterminate::-moz-progress-bar {
  background: var(--_indeterminate-track);
  background-size: var(--_indeterminate-track-size);
  background-position: right; 
  animation: var(--_indeterminate-track-animation);
}

JavaScript

JavaScript memainkan peran penting dengan elemen <progress>. Atribut ini mengontrol nilai yang dikirim ke elemen dan memastikan tersedia cukup informasi dalam dokumen untuk pembaca layar.

const state = {
  val: null
}

Demo menawarkan tombol untuk mengontrol progresnya, yaitu memperbarui state.val, lalu memanggil fungsi untuk memperbarui DOM.

document.querySelector('#complete').addEventListener('click', e => {
  state.val = 1
  setProgress()
})

setProgress()

Fungsi ini adalah tempat orkestrasi UI/UX terjadi. Mulai dengan membuat fungsi setProgress(). Tidak ada parameter yang diperlukan karena memiliki akses ke objek state, elemen progres, dan zona <main>.

const setProgress = () => {
  
}

Menyetel status pemuatan di zona <main>

Bergantung pada apakah progresnya selesai atau tidak, elemen <main> terkait memerlukan update pada atribut aria-busy:

const setProgress = () => {
  zone.setAttribute('aria-busy', state.val < 1)
}

Hapus atribut jika jumlah pemuatan tidak diketahui

Jika nilai tidak diketahui atau tidak ditetapkan, null dalam penggunaan ini, hapus atribut value dan aria-valuenow. Tindakan ini akan mengubah <progress> menjadi tidak tentu.

const setProgress = () => {
  zone.setAttribute('aria-busy', state.val < 1)

  if (state.val === null) {
    progress.removeAttribute('aria-valuenow')
    progress.removeAttribute('value')
    progress.focus()
    return
  }
}

Memperbaiki masalah matematika desimal JavaScript

Karena saya memilih untuk tetap menggunakan nilai default progres 1, fungsi penambahan dan penurunan demo menggunakan matematika desimal. JavaScript, dan bahasa lainnya, tidak selalu mahir dalam hal itu. Berikut adalah fungsi roundDecimals() yang akan memangkas kelebihan hasil dari hasil matematika:

const roundDecimals = (val, places) =>
  +(Math.round(val + "e+" + places)  + "e-" + places)

Bulatkan nilai agar dapat ditampilkan dan mudah dibaca:

const setProgress = () => {
  zone.setAttribute('aria-busy', state.val < 1)

  if (state.val === null) {
    progress.removeAttribute('aria-valuenow')
    progress.removeAttribute('value')
    progress.focus()
    return
  }

  const val = roundDecimals(state.val, 2)
  const valPercent = val * 100 + "%"
}

Menetapkan nilai untuk pembaca layar dan status browser

Nilai tersebut digunakan di tiga lokasi dalam DOM:

  1. Atribut value milik elemen <progress>.
  2. Atribut aria-valuenow.
  3. Konten teks dalam <progress>.
const setProgress = () => {
  zone.setAttribute('aria-busy', state.val < 1)

  if (state.val === null) {
    progress.removeAttribute('aria-valuenow')
    progress.removeAttribute('value')
    progress.focus()
    return
  }

  const val = roundDecimals(state.val, 2)
  const valPercent = val * 100 + "%"

  progress.value = val
  progress.setAttribute('aria-valuenow', valPercent)
  progress.innerText = valPercent
}

Memberikan fokus pada progres

Dengan nilai yang diperbarui, pengguna yang mampu melihat akan melihat perubahan progres, tetapi pengguna pembaca layar belum diberi pengumuman perubahan. Fokuskan elemen <progress> dan browser akan mengumumkan update!

const setProgress = () => {
  zone.setAttribute('aria-busy', state.val < 1)

  if (state.val === null) {
    progress.removeAttribute('aria-valuenow')
    progress.removeAttribute('value')
    progress.focus()
    return
  }

  const val = roundDecimals(state.val, 2)
  const valPercent = val * 100 + "%"

  progress.value = val
  progress.setAttribute('aria-valuenow', valPercent)
  progress.innerText = valPercent

  progress.focus()
}

Screenshot aplikasi Voice Over Mac OS 
  yang membacakan progres panel pemuatan kepada pengguna.

Kesimpulan

Setelah Anda tahu cara saya melakukannya, bagaimana Anda‽ 🙂

Tentu saja ada beberapa perubahan yang ingin saya lakukan jika diberi kesempatan lain. Saya pikir masih ada ruang untuk membersihkan komponen saat ini, dan ruang untuk mencoba membangunnya tanpa batasan gaya class pseudo elemen <progress>. Boleh dijelajahi!

Mari lakukan diversifikasi pendekatan dan pelajari semua cara untuk membangun di web.

Buat demo, link tweet me, dan saya akan menambahkannya ke bagian remix komunitas di bawah.

Remix komunitas