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

<progress> öğesi ile renk uyarlamalı ve erişilebilir bir yükleme çubuğu oluşturma hakkında temel bilgiler.

Bu yayında, <progress> öğesini kullanarak renk uyumlu ve erişilebilir bir yükleme çubuğu oluşturma konusundaki düşüncelerimi paylaşmak istiyorum. Demoyu deneyin ve kaynağı görüntüleyin.

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

Videoyu tercih ediyorsanız bu yayının YouTube sürümünü burada bulabilirsiniz:

Genel Bakış

<progress> öğesi, kullanıcılara tamamlama hakkında görsel ve işitsel geri bildirim sağlar. Bu görsel geri bildirim, formdaki ilerleme durumu, indirme veya yükleme bilgilerinin gösterilmesi ya da ilerleme miktarının bilinmediği ancak çalışmanın devam ettiği durumların gösterilmesi gibi senaryolar için değerlidir.

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

Her tarayıcıda, uyarlanabilir simgenin üstten alta genel görünümünü sunan açık ve koyu sekmeler: Safari, Firefox, Chrome.
Firefox, Safari, iOS Safari, Chrome ve Android Chrome'da açık ve koyu temalarda gösterilen demo.

Brüt kar

Açık ilişki özelliklerini atlayıp örtülü ilişki kullanabilmek için <progress> öğesini <label> içine sarmaladım. Ayrıca, yükleme durumundan etkilenen bir üst öğeyi etiketledim. Böylece ekran okuyucu teknolojileri bu bilgileri kullanıcıya iletebilir.

<progress></progress>

value yoksa öğenin ilerleme durumu belirsiz olur. 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 ayarlamak aralığı 0-100 olarak ayarlar. 0 ile 1 sınırları arasında kalmayı tercih ederek ilerleme değerlerini 0, 5 veya %50 olarak çevirdim.

Etiket sarmalanmış ilerleme durumu

Örtük ilişkide ilerleme öğesi şuna benzer bir etiketle sarmalanır:

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

Demomda etiketi yalnızca ekran okuyucular için eklemeyi seçtim. Bu işlem, etiket metnini bir <span> içine sarmalayarak ve ekranda görünmeyecek şekilde bazı stiller uygulayarak yapılır:

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

Yalnızca ekrana hazır öğesini gösteren devtools ekran görüntüsü.

Yükleme ilerleme durumundan etkilenen alan

İyi bir görüşünüz varsa ilerleme göstergelerini ilgili öğelerle ve sayfa alanlarıyla ilişkilendirmek kolay olabilir ancak görme engelli kullanıcılar için bu durum 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ındaki ilişkiyi belirtin.

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

JavaScript'de, görevin başında aria-busytrue'e, bittikten sonra false'e getirin.

Aria özelliği eklemeleri

<progress> öğesinin örtük rolü progressbar olsa da bu örtük role sahip olmayan tarayıcılar için açık rol olarak ayarladım. Ayrıca, öğeyi bilinmeyen bir duruma açıkça yerleştirmek için indeterminate özelliğini de ekledim. Bu, öğenin value değerinin ayarlanmadığını gözlemlemekten daha net bir durumdur.

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

İlerleme öğesini JavaScript'den odaklanılabilir hale getirmek için tabindex="-1" değerini kullanın. İlerleme değiştikçe ilerleme durumuna odaklanmak, kullanıcıya güncellenmiş ilerlemenin ne kadara ulaştığını bildireceğinden ekran okuyucu teknolojisi için önemlidir.

Stiller

İlerleme öğesi, stillendirme açısından biraz karmaşıktır. Yerleşik HTML öğelerinin, seçimi zor olabilecek özel gizli bölümleri vardır ve genellikle ayarlanabilecek sınırlı sayıda özellik sunar.

Düzen

Düzen stilleri, ilerleme öğesinin boyutu ve etiket konumu konusunda biraz esneklik sağlamak için tasarlanmıştır. Yararlı ancak gerekli olmayan ek bir görsel ipucu olabilecek özel bir tamamlama durumu eklenir.

<progress> Düzen

İlerleme öğesinin genişliği, tasarımda gereken alanla birlikte küçülebileceği ve büyüyebileceği için değiştirilmez. Yerleşik stiller, appearance ve border değerleri none olarak ayarlanarak kaldırılır. Bu, her tarayıcıda öğe 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 üzere bilimsel sayı gösterimini kullanır. Bu nedenle border-radius her zaman yuvarlanır. 1000px değerine eş değer. Ayarlayıp unutabileceğim kadar büyük bir değer kullanmak istediğim için bunu kullanmayı tercih ediyorum (ayrıca 1000px'ten daha kısa yazılıyor). Gerekirse daha da büyütmek de kolaydır: 3'ü 4'e değiştirdiğinizde 1e4px, 10000px ile eşdeğer olur.

overflow: hidden kullanılıyor ve tartışmalı bir stil. Bu, border-radius değerlerini parçaya ve parça doldurma öğelerine aktarmak gerekmemesi gibi bazı şeyleri kolaylaştırdı ancak ilerlemenin hiçbir alt öğesinin öğenin dışında bulunamayacağı anlamına da geliyordu. Bu özel ilerleme öğesi için overflow: hidden olmadan başka bir iterasyon yapılabilir. Bu, animasyonlar veya daha iyi tamamlama durumları için bazı fırsatlar sunabilir.

İşlem tamamlandı

CSS seçicileri, maksimum değeri değerle karşılaştırarak zor işi yapar. Eşleşme varsa ilerleme tamamlanır. İşlem tamamlandığında, bir sözde öğe oluşturulur ve ilerleme öğesinin sonuna eklenir. Bu öğe, işlemin tamamlandığına dair güzel bir ek 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);
}

%100&#39;e ulaşmış ve sonunda onay işareti gösteren yükleme çubuğunun ekran görüntüsü.

Renk

Tarayıcı, ilerleme öğesi için kendi renklerini getirir ve yalnızca bir CSS özelliğiyle açık ve koyu modlara 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 temaya uyumlu bir <progress> öğesine dönüştürmek için tek yapmanız gereken color-scheme seçeneğini etkinleştirmektir.

progress {
  color-scheme: light dark;
}

Tek tesis ilerleme dolgu rengi

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

progress {
  accent-color: rebeccapurple;
}

Parçanın arka plan renginin, accent-color değerine bağlı olarak açıktan koyuya değiştiğini fark edin. Tarayıcı, doğru kontrastı sağlıyor: Oldukça şık.

Tamamen özel açık ve koyu renkler

<progress> öğesinde biri parça rengi, diğeri parça ilerleme rengi olmak üzere iki özel özellik ayarlayın. 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%);
  }
}

Odaklanma stilleri

Daha önce, programatik olarak odaklanabilmesi için öğeye negatif bir sekme dizini vermiştik. Daha akıllı odak halkası stilini etkinleştirmek için odağı özelleştirmek üzere :focus-visible simgesini kullanın. Bu durumda, fare tıklaması ve odak, odak halkasını göstermez ancak klavye tıklamaları gösterir. Bu konuyu daha ayrıntılı olarak ele alan YouTube videosunu incelemenizi öneririz.

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

Yükleme çubuğunun etrafında odak halkası bulunan ekran görüntüsü. Renklerin tümü eşleşiyor.

Tarayıcılar genelinde özel stiller

<progress> öğesinin her tarayıcının gösterdiği bölümlerini seçerek stilleri özelleştirin. İlerleme öğesi tek bir etikettir ancak CSS sözde seçicileri aracılığıyla gösterilen birkaç alt öğeden oluşur. Chrome Geliştirici Araçları, ayarı etkinleştirirseniz aşağıdaki öğeleri gösterir:

  1. Sayfanızı sağ tıklayıp Öğeyi İncele'yi seçerek Geliştirici Araçları'nı açın.
  2. DevTools 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.

Kullanıcı aracısı gölge DOM&#39;unu göstermenin DevTools&#39;taki yerini gösteren 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, daha önce oluşturulan ve açık ve koyuya uyum sağlayan özel özellikleri kullanarak background-color'ü 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, yalnızca <progress> öğesinde ::-moz-progress-bar sözde seçicisini gösterir. Bu, parçayı doğrudan renklendiremeyeceğimiz anlamına da gelir.

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

Firefox&#39;un ekran görüntüsü ve ilerleme öğesi bölümlerinin bulunduğu yer.

Safari, iOS Safari, Firefox, Chrome ve Android&#39;deki Chrome&#39;un yükleme çubuğunun çalıştığı gösterilen Hata Ayıklama Köşesi ekran görüntüsü.

Firefox'un accent-color olarak ayarlanmış bir parça rengine sahipken iOS Safari'nin açık mavi bir parça rengine sahip olduğunu unutmayın. Koyu modda da aynıdır: Firefox'ta koyu bir parça vardır ancak belirlediğimiz özel renk değil ve Webkit tabanlı tarayıcılarda çalışır.

Animasyon

Tarayıcı yerleşik sözde seçicileriyle çalışırken genellikle izin verilen CSS özelliklerinin sınırlı bir kümesiyle çalışırsınız.

Parçanın dolmasını animasyonlu olarak gösterme

İlerleme öğesinin inline-size bölümüne geçiş eklemek Chromium'da işe yarar ancak Safari'de işe yaramaz. Firefox, ::-moz-progress-bar özelliğinde geçiş özelliği de kullanmaz.

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

:indeterminate durumunu animasyonlu hale getirme

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

Özel özellikler

Özel özellikler birçok şey için mükemmeldir ancak en sevdiğim özelliklerden biri, büyülü görünen bir CSS değerine isim vermektir. Aşağıda, oldukça karmaşık ancak güzel bir ada sahip bir linear-gradient gösterilmektedir. Amacını ve kullanım alanlarını net bir şekilde anlayabilmelisiniz.

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, bu tarayıcıya özgü seçicileri bir araya getiremeyeceğimiz için kodun DRY kalmasına da yardımcı olur.

Animasyon kareleri

Amaç, ileri geri giden sonsuz bir animasyon oluşturmaktır. Başlangıç ve bitiş ana kareleri CSS'de ayarlanır. Başlangıç noktasına tekrar tekrar dönen bir animasyon oluşturmak için yalnızca bir animasyon karesi (50%'teki orta animasyon karesi) gerekir.

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

Her tarayıcıyı hedefleme

Bazı tarayıcılar, <progress> öğesinin kendisinde sözde öğe oluşturmaya veya ilerleme çubuğunun animasyonlu olmasına izin vermez. Parçanın animasyonu, sözde öğelerden daha fazla tarayıcı tarafından desteklenir. Bu nedenle, temel olarak sözde öğelerden animasyonlu çubuklara geçiş yapıyorum.

Chromium sözde öğesi

Chromium, öğeyi kapsayacak şekilde konumla birlikte kullanılan ::after sözde öğesine izin verir. Belirsiz özel özellikler kullanılır ve ileri geri animasyon ç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 bir 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 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> öğesinde önemli bir rol oynar. Öğeye gönderilen değeri kontrol eder ve belgede ekran okuyucular için yeterli bilginin bulunduğundan emin olur.

const state = {
  val: null
}

Demoda, ilerlemeyi kontrol etmek için düğmeler sunulur. Bu düğmeler state.val öğesini 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 koordinasyonunun gerçekleştiği yer bu işlevdir. setProgress() işlevi oluşturarak başlayın. state nesnesine, ilerleme öğesine ve <main> bölgesine erişimi olduğundan parametreye gerek yoktur.

const setProgress = () => {
  
}

<main> bölgesindeki yükleme durumunu ayarlama

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

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

Yükleme miktarı bilinmiyorsa özellikleri temizleme

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

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 durumunun varsayılan maksimum değerini 1 olarak kullanmayı tercih ettiğim için demo artma ve eksiltme işlevleri ondalık matematik kullanır. JavaScript ve diğer diller bu konuda her zaman iyi değildir. Aşağıda, matematik sonucunun fazlalıklarını kırpacak bir roundDecimals() işlevi verilmiştir:

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

Değeri, sunulabilecek ve okunaklı olacak şekilde 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 üç yerde 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örme engeli olmayan 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 odaklanın. 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()
}

Yükleme çubuğunun ilerleme durumunu kullanıcıya okuyan Mac OS Voice Over uygulamasının ekran görüntüsü.

Sonuç

Bunu nasıl yaptığımı öğrendiğinize göre, siz ne yapardınız? 🙂

Tekrar şansım olsaydı kesinlikle birkaç değişiklik yapmak isterdim. Mevcut bileşeni temizlemek ve <progress> öğesinin sözde sınıf stili sınırlamaları olmadan bir bileşen oluşturmaya çalışmak için yer olduğunu düşünüyorum. Bu konuyu incelemeye değer.

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

Bir demo oluşturun, bağlantılarını bana tweetleyin. Ardından, aşağıdaki topluluk remiksleri bölümüne ekleyeceğim.

Topluluk remiksleri