Yükleme çubuğu bileşeni oluşturma

<progress> öğesiyle renge uyum sağlayan ve erişilebilir bir yükleme çubuğu oluşturmaya dair temel bir genel bakış.

Bu yayında, <progress> öğesiyle renge uyum sağlayan ve erişilebilir bir yükleme çubuğu oluşturma hakkındaki düşüncelerimi paylaşmak istiyorum. Demoyu deneyin ve kaynağı görüntüleyin.

Chrome'da açık ve koyu, belirsiz, artan ve tamamlanma durumları gösteriliyor.

Video izlemeyi tercih ediyorsanız bu yayının YouTube versiyonunu izleyebilirsiniz:

Genel Bakış

<progress> öğesi, kullanıcılara işlemin tamamlandığına dair görsel ve işitsel geri bildirim sağlar. Bu görsel geri bildirim; form doldurma, indirme veya yükleme bilgilerini gösterme, hatta ilerleme miktarının bilinmediğini ancak çalışmanın devam ettiğini gösterme gibi senaryolarda faydalıdır.

Bu GUI Challenge, erişilebilirlik konusunda biraz çaba harcamak için mevcut HTML <progress> öğesiyle çalıştı. Renkler ve düzenler, bileşeni modernize etmek ve tasarım sistemlerine daha iyi uymasını sağlamak için yerleşik öğenin özelleştirme sınırlarını zorlar.

Her tarayıcıda uyarlanabilir simgeye yukarıdan aşağıya genel bakış sunan açık ve koyu sekmeler: Safari, Firefox, Chrome.
Firefox, Safari, iOS Safari, Chrome ve Android Chrome'da açık ve koyu şemalarda gösterilen demo.

Brüt kar

Dolaylı bir ilişki için açık ilişki özelliklerini atlayabilmek amacıyla <progress> öğesini bir <label> içine sarmayı seçtim. Ayrıca, yükleme durumundan etkilenen bir üst öğeyi etiketledim. Böylece ekran okuyucu teknolojileri bu bilgiyi kullanıcıya iletebilir.

<progress></progress>

value yoksa öğenin ilerleme durumu belirsizdir. max özelliği varsayılan olarak 1 değerine ayarlanır. Bu nedenle ilerleme 0 ile 1 arasındadır. Örneğin, max değerini 100 olarak ayarlarsanız aralık 0-100 olur. 0 ve 1 sınırları içinde kalmayı seçtim.İlerleme değerlerini 0, 5 veya %50 olarak çevirdim.

Etiketle sarmalanmış ilerleme

Dolaylı bir ilişkide, ilerleme öğesi aşağıdaki gibi bir etiketle sarmalanır:

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

Demomda etiketi yalnızca ekran okuyucular için eklemeyi seçtim. Bunu yapmak için etiket metnini bir <span> içine alın ve bazı stiller uygulayarak ekranın dışında kalmasını sağlayın:

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

WebAIM'den aşağıdaki CSS ile:

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

Geliştirici araçlarının, yalnızca ekrana hazır öğesini gösteren ekran görüntüsü.

Yükleme işleminden etkilenen alan

Görme sorununuz yoksa ilerleme göstergesini ilgili öğeler ve sayfa alanlarıyla ilişkilendirmek kolay olabilir ancak görme engelli kullanıcılar için bu o kadar net değildir. Yükleme tamamlandığında değişecek en üstteki öğeye aria-busy özelliğini atayarak bu durumu iyileştirin. Ayrıca, aria-describedby ile ilerleme durumu ve yükleme bölgesi arasında bir ilişki olduğunu belirtin.

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

JavaScript'te, görevin başında aria-busy düğmesini true konumuna, görev tamamlandığında ise false konumuna getirin.

ARIA özelliği eklemeleri

<progress> öğesinin örtük rolü progressbar olsa da bu örtük role sahip olmayan tarayıcılar için bunu açık hale getirdim. Ayrıca, öğeyi açıkça bilinmeyen bir duruma getirmek için indeterminate özelliğini ekledim. Bu, öğenin value değerinin ayarlanmadığını gözlemlemekten daha nettir.

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

İlerleme öğesini JavaScript'ten odaklanabilir hale getirmek için tabindex="-1" kullanın. Bu, ekran okuyucu teknolojisi için önemlidir. İlerleme değiştikçe ilerlemeye odaklanmak, güncellenen ilerlemenin ne kadar ilerlediğini kullanıcıya duyurur.

Stiller

İlerleme öğesinin stilini belirlemek biraz zordur. Yerleşik HTML öğelerinin, seçilmesi zor olabilen ve genellikle yalnızca sınırlı sayıda özellik sunan özel gizli bölümleri vardır.

Düzen

Düzen stilleri, ilerleme öğesinin boyutunda ve etiket konumunda bir miktar esneklik sağlamak için tasarlanmıştır. Özel bir tamamlanma durumu eklenir. Bu durum, zorunlu olmasa da faydalı bir ek görsel ipucu olabilir.

<progress> düzeni

İlerleme öğesinin genişliği, tasarımda gereken alana göre küçülüp büyüyebilmesi için olduğu gibi bırakılır. Yerleşik stiller, appearance ve border none olarak ayarlanarak kaldırılır. Bu, her tarayıcının kendi öğesi için kendi stilleri olduğundan öğenin tarayıcılar arasında normalleştirilebilmesi için yapılır.

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

_radius için 1e3px değeri, büyük bir sayıyı ifade etmek için bilimsel sayı gösterimini kullanır. Bu nedenle border-radius her zaman yuvarlanır. 1000px ile eşdeğerdir. Bunu kullanmayı seviyorum çünkü amacım, ayarlayıp unutabileceğim kadar büyük bir değer kullanmak (ve 1000px değerinden daha kısa yazılıyor). Gerekirse daha da büyütmek kolaydır: 3'ü 4 olarak değiştirmeniz yeterlidir, ardından 1e4px, 10000px değerine eşdeğer olur.

overflow: hidden kullanılır ve tartışmalı bir stildir. Bu sayede, border-radius değerlerini parçaya ve parça dolgu öğelerine aktarmak gerekmediği için birkaç işlem kolaylaştı. Ancak bu, ilerleme çubuğunun alt öğelerinin öğenin dışında bulunamayacağı anlamına da geliyordu. Bu özel ilerleme öğesinin başka bir iterasyonu overflow: hidden olmadan yapılabilir ve animasyonlar veya daha iyi tamamlama durumları için bazı fırsatlar sunabilir.

İşlem tamamlandı

CSS seçiciler, maksimum değeri değerle karşılaştırarak zorlu işi yapar ve eşleşme varsa ilerleme tamamlanır. İşlem tamamlandığında bir sözde öğe oluşturulur ve ilerleme öğesinin sonuna eklenir. Bu, işlemin tamamlandığına dair hoş bir görsel ipucu sağlar.

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);
}

Yükleme çubuğunun %100'e ulaştığını ve sonunda bir onay işareti gösterdiğini gösteren ekran görüntüsü.

Renk

Tarayıcı, ilerleme öğesi için kendi renklerini getirir ve tek bir CSS özelliğiyle açık ve koyu renklere uyum sağlar. Bu, tarayıcıya özel bazı seçicilerle geliştirilebilir.

Açık ve koyu tarayıcı stilleri

Sitenizi koyu ve açık renk uyumlu bir <progress> öğesine dahil etmek için color-scheme yeterlidir.

progress {
  color-scheme: light dark;
}

Tek mülk ilerleme durumu dolgulu rengi

<progress> öğesini renklendirmek için accent-color kullanın.

progress {
  accent-color: rebeccapurple;
}

Parça arka plan renginin accent-color değerine göre açıktan koyuya değiştiğine dikkat edin. Tarayıcı, uygun kontrastı sağlıyor. Bu oldukça iyi bir özellik.

Tamamen özelleştirilebilen açık ve koyu renkler

<progress> öğesinde iki özel özellik ayarlayın. Bunlardan biri parça rengi, diğeri ise parça ilerleme rengi için kullanılır. prefers-color-scheme medya sorgusunda, parça ve parça ilerleme durumu için yeni renk değerleri sağlayın.

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%);
  }
}

Odak stilleri

Daha önce, öğeye negatif bir sekme dizini vermiştik. Böylece öğeye programatik olarak odaklanılabiliyordu. Daha akıllı odak halkası stilini etkinleştirmek için odağı özelleştirmek üzere :focus-visible kullanın. Bu durumda, fare tıklaması ve odaklama yapıldığında odak halkası gösterilmez ancak klavye tıklamalarında gösterilir. YouTube videosu bu konuyu daha ayrıntılı bir şekilde ele alıyor ve incelemeye değer.

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

Etrafında odak halkası olan yükleme çubuğunun ekran görüntüsü. Renklerin hepsi eşleşiyor.

Tarayıcılar arasında özel stiller

Her tarayıcının gösterdiği <progress> öğesinin parçalarını seçerek stilleri özelleştirin. İlerleme öğesi tek bir etiket olarak kullanılır ancak CSS sözde seçicileri aracılığıyla gösterilen birkaç alt öğeden oluşur. Chrome Geliştirici Araçları, ayarı etkinleştirirseniz bu öğeleri gösterir:

  1. Sayfanızı sağ tıklayın ve Geliştirici Araçları'nı açmak için Öğeyi İncele'yi seçin.
  2. Geliştirici Araçları penceresinin sağ üst köşesindeki Ayarlar dişli simgesini tıklayın.
  3. Öğeler başlığı altında Kullanıcı aracısı gölge DOM'unu göster onay kutusunu bulup etkinleştirin.

Geliştirici Araçları'nda kullanıcı aracısı gölge DOM'unu göstermeyi etkinleştirme seçeneğinin bulunduğu yerin ekran görüntüsü.

Safari ve Chromium stilleri

Safari ve Chromium gibi WebKit tabanlı tarayıcılar, CSS'nin bir alt kümesinin kullanılmasına olanak tanıyan ::-webkit-progress-bar ve ::-webkit-progress-value öğelerini gösterir. Şimdilik background-color değerini, daha önce oluşturulan ve açık ile koyu temaya uyum sağlayan özel özellikleri kullanarak ayarlayın.

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

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

İlerleme öğesinin iç öğelerini gösteren ekran görüntüsü.

Firefox stilleri

Firefox, ::-moz-progress-bar sözde seçicisini yalnızca <progress> öğesinde gösterir. Bu, parçayı doğrudan renklendiremeyeceğimiz anlamına da gelir.

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

Firefox'un ve ilerleme öğesi parçalarının nerede bulunacağını gösteren ekran görüntüsü.

Safari, iOS Safari, Firefox, Chrome ve Android'de Chrome'un yükleme çubuğunun çalıştığını gösteren Hata Ayıklama Köşesi'nin ekran görüntüsü.

Firefox'un accent-color tarafından ayarlanmış bir parça rengi varken iOS Safari'nin açık mavi bir parçası olduğuna dikkat edin. Koyu modda da aynı durum geçerlidir: Firefox'ta koyu bir parça vardır ancak ayarladığımız özel renk yoktur ve Webkit tabanlı tarayıcılarda çalışır.

Animasyon

Tarayıcıda yerleşik sözde seçicilerle çalışırken genellikle izin verilen CSS özelliklerinin sınırlı bir grubuyla çalışılır.

Parçanın dolma animasyonu

İlerleme öğesinin inline-size kısmına geçiş eklemek Chromium'da işe yarar ancak Safari'de işe yaramaz. Firefox da ::-moz-progress-bar öğesinde geçiş özelliği kullanmaz.

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

:indeterminate durumu animasyona dönüştürülüyor

Burada biraz daha yaratıcı davranarak animasyon ekliyorum. Chromium için bir sözde öğe oluşturulur ve üç tarayıcıda da ileri geri animasyonlu bir gradyan uygulanır.

Özel özellikler

Özel özellikler birçok açıdan faydalıdır. En sevdiğim özelliklerinden biri, büyülü gibi görünen bir CSS değerine ad vermesidir. Aşağıda oldukça karmaşık bir linear-gradient var ancak adı güzel. Amacı ve kullanım alanları net bir şekilde anlaşılabilir.

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

Özel özellikler, kodun DRY kalmasına da yardımcı olur. Tarayıcıya özel bu seçicileri bir araya gruplandıramayız.

Animasyon kareleri

Hedef, ileri geri giden sonsuz bir animasyon oluşturmaktır. Başlangıç ve bitiş kareleri CSS'de ayarlanır. Başladığı yere tekrar tekrar dönen bir animasyon oluşturmak için yalnızca bir animasyon karesi (50% konumundaki orta animasyon karesi) gerekir.

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

Her tarayıcıyı hedefleme

Her tarayıcı, <progress> öğesinin kendisinde sözde öğe oluşturulmasına veya ilerleme çubuğunun animasyonlu olmasına izin vermez. Çubuğun animasyonunu destekleyen tarayıcı sayısı, sözde öğeyi destekleyenlerden daha fazla. Bu nedenle, sözde öğelerden çubuk animasyonuna geçiş yapıyorum.

Chromium sözde öğesi

Chromium, öğeyi kapsayacak bir konumla kullanılan ::after sözde öğesine izin verir. Belirsiz özel özellikler kullanılır ve ileri geri animasyonu çok iyi çalışır.

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);
}
Safari ilerleme çubuğu

Safari'de, özel özellikler ve animasyon, sözde öğe ilerleme çubuğuna uygulanır:

progress:indeterminate::-webkit-progress-bar {
  background: var(--_indeterminate-track);
  background-size: var(--_indeterminate-track-size);
  background-position: right; 
  animation: var(--_indeterminate-track-animation);
}
Firefox ilerleme çubuğu

Firefox için özel özellikler ve bir animasyon, sözde öğe ilerleme çubuğuna da uygulanır:

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, <progress> öğesiyle önemli bir rol oynar. Öğeye gönderilen değeri kontrol eder ve dokümanda ekran okuyucular için yeterli bilgi bulunmasını sağlar.

const state = {
  val: null
}

Demoda, ilerleme durumunu kontrol etmek için düğmeler bulunur. Bu düğmeler state.val değerini günceller ve ardından DOM'u güncellemek için bir işlev çağırır.

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

setProgress()

Kullanıcı arayüzü/kullanıcı deneyimi düzenlemesi bu işlevde gerçekleşir. setProgress() işlevi oluşturarak başlayın. state nesnesine, ilerleme öğesine ve <main> bölgesine erişimi olduğu için parametre gerekmez.

const setProgress = () => {
  
}

<main> bölgesinde yükleme durumu ayarlanıyor

İlerleme durumunun tamamlanıp tamamlanmadığına bağlı olarak, ilgili <main> öğesinin aria-busy özelliğinin güncellenmesi gerekir:

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

Yükleme tutarı bilinmiyorsa özellikleri temizle

Değer bilinmiyorsa veya ayarlanmamışsa bu kullanımda null, value ve aria-valuenow özelliklerini kaldırın. Bu işlem, <progress> öğesini belirsiz hale getirir.

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

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

JavaScript ondalık matematik sorunlarını düzeltme

İlerleme çubuğunun varsayılan maksimum değerini 1 olarak bıraktığım için demo artırma ve azaltma işlevleri ondalık matematik kullanıyor. JavaScript ve diğer diller, bu konuda her zaman başarılı değildir. Matematiksel sonucun fazlalıklarını kırpacak bir roundDecimals() işlevi aşağıda verilmiştir:

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

Değeri, sunulabilmesi ve okunabilir olması için yuvarlayın:

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 + "%"
}

Ekran okuyucular ve tarayıcı durumu için değer ayarlama

Değer, DOM'da üç konumda kullanılır:

  1. <progress> öğesinin value özelliği.
  2. aria-valuenow özelliği.
  3. <progress> iç metin içeriği.
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
}

İlerleme durumuna odaklanma

Değerler güncellendiğinde, gören kullanıcılar ilerleme durumunun değiştiğini görür ancak ekran okuyucu kullanıcılarına henüz değişiklik duyurusu yapılmamıştır. <progress> öğesine odaklandığınızda tarayıcı güncellemeyi duyurur.

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()
}

Mac OS Voice Over uygulamasının, yükleme çubuğunun ilerleme durumunu kullanıcıya okuduğunu gösteren ekran görüntüsü.

Sonuç

Benim nasıl yaptığımı gördünüz. Siz nasıl yapardınız? 🙂

Bir şansım daha olsaydı kesinlikle birkaç değişiklik yapardım. Mevcut bileşeni temizlemek ve <progress> öğesinin sözde sınıf stil sınırlamaları olmadan bir bileşen oluşturmaya çalışmak için yer olduğunu düşünüyorum. Bu konuyu incelemenizi öneririz.

Yaklaşımlarımızı çeşitlendirelim ve web'de geliştirme yapmanın tüm yollarını öğrenelim.

Bir demo oluşturun, bağlantıları bana tweetleyin. Ben de aşağıdaki topluluk remiksleri bölümüne ekleyeyim.

Topluluk remiksleri