Tworzenie komponentu paska wczytywania

Podstawowy przegląd tworzenia paska ładowania z dostępną kolorystyką za pomocą elementu <progress>.

W tym poście chcę podzielić się z Wami sposobem na stworzenie paska wczytywania, który dostosowuje się do kolorów i jest dostępny dla osób z ograniczonymi możliwościami. Wypróbuj wersję demonstracyjnąwyświetl kod źródłowy.

Demo w Chrome pokazujące jasne i ciemne, nieokreślone, rosnące i ukończone wskaźniki.

Jeśli wolisz film, oto wersja tego posta w YouTube:

Omówienie

Element <progress> dostarcza użytkownikom wizualne i dźwiękowe informacje o ukończeniu projektu. Takie wizualne informacje zwrotne są przydatne w różnych sytuacjach, np. podczas wypełniania formularza, wyświetlania informacji o pobieraniu lub przesyłaniu czy nawet informowania o tym, że postęp jest nieznany, ale praca nadal trwa.

To wyzwanie GUI współpracowało z istniejącym elementem HTML <progress>, co pozwoliło zaoszczędzić trochę wysiłku w zakresie ułatwień dostępu. Kolory i układy przesuwają granice dostosowywania wbudowanego elementu, aby unowocześnić komponent i ułatwić jego dopasowanie do systemów projektowania.

Jasne i ciemne karty w każdej przeglądarce z widokiem ikony adaptacyjnej wyświetlane od góry do dołu: Safari, Firefox, Chrome.
Wersja demonstracyjna widoczna w przeglądarkach Firefox, Safari, iOS Safari, Chrome oraz Chrome na Androida w trybie jasnym i ciemnym.

Znacznik

Element <progress> został zawinięty w element <label>, aby można było pominąć jawne atrybuty relacji na rzecz niejawnej relacji. Oznaczyłem też element nadrzędny, na który wpływa stan wczytywania, aby czytniki ekranu mogły przekazać te informacje użytkownikowi.

<progress></progress>

Jeśli nie ma wartości value, postęp elementu jest nieokreślony. Atrybut max ma domyślnie wartość 1, więc postęp mieści się w przedziale od 0 do 1. Ustawienie wartości max na 100 ustawi zakres na 0–100. Wybrałem, aby wartości znajdowały się w zakresie od 0 do 1, a wartości postępu były przekształcane na 0,5 lub 50%.

Postęp w ramach etykiety

W przypadku relacji domyślnej element postępu jest otoczony etykietą w taki sposób:

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

W mojej wersji demonstracyjnej uwzględniono etykietę tylko dla czytników ekranu. Aby to zrobić, użyj elementu <span> i zastosuj do niego odpowiednie style, aby był niewidoczny na ekranie:

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

z usługą porównywania cen, która zawiera następujący kod CSS z 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;
}

Zrzut ekranu narzędzi programistycznych pokazujący element widoczny tylko na ekranie.

Obszar dotknięty przez postęp wczytywania

Jeśli masz zdrowy wzrok, łatwo powiązać wskaźnik postępu z powiązanymi elementami i obszarami strony, ale dla użytkowników z wadą wzroku to nie jest takie oczywiste. Aby to poprawić, przypisz atrybut aria-busy do elementu znajdującego się najwyżej, który zmieni się po zakończeniu wczytywania. Ponadto należy wskazać związek między postępem a strefą wczytywania za pomocą aria-describedby.

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

W JavaScriptu przełącz aria-busy na true na początku zadania, a na false – po jego zakończeniu.

Dodania atrybutów Aria

Chociaż domyślna rola elementu <progress> to progressbar, w przypadku przeglądarek, które nie mają tej domyślnej roli, dodałem ją wprost. Dodaliśmy też atrybut indeterminate, aby wyraźnie ustawić stan elementu jako nieznany. Jest to wyraźniejsze niż obserwowanie, że element nie ma ustawionego atrybutu value.

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

Użyj elementu tabindex="-1", aby umożliwić skupienie na elemencie postępu z poziomu kodu JavaScript. Jest to ważne dla technologii czytników ekranu, ponieważ po zmianie postępu w procesie będzie ono informować użytkownika o tym, jak daleko zaawansował.

Style

Element postępu jest nieco problematyczny pod względem stylizacji. Wbudowane elementy HTML mają specjalne ukryte części, które mogą być trudne do wybrania, i często oferują tylko ograniczony zestaw właściwości do ustawienia.

Układ

Style układu mają zapewnić pewną elastyczność w zakresie rozmiaru elementu postępu i pozycji etykiety. Dodano specjalny stan zakończenia, który może być przydatnym, ale nieobowiązkowym dodatkowym sygnałem wizualnym.

<progress> Układ

Szerokość elementu postępu pozostaje bez zmian, aby mógł się kurczyć i rozszerzać wraz z przestrzenią potrzebną w projektie. Style wbudowane zostaną usunięte przez ustawienie appearance i border na none. Dzięki temu element może być normalizowany w różnych przeglądarkach, ponieważ każda przeglądarka ma własne style dla tego elementu.

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

Wartość 1e3px w polu _radius używa notacji liczbowej do wyrażenia dużej liczby, więc border-radius jest zawsze zaokrąglana. To 1000px. Lubię używać tej metody, ponieważ moim celem jest użycie wartości wystarczająco dużej, aby można ją było ustawić i zapomnieć (a dodatkowo jest ona krótsza do zapisania niż 1000px). W razie potrzeby można ją też łatwo zwiększyć: wystarczy zmienić 3 na 4, a 1e4px będzie się równać 10000px.

Używany jest styl overflow: hidden, który wzbudza kontrowersje. Ułatwiło to kilka rzeczy, np. nie trzeba było przekazywać wartości border-radius do ścieżki ani elementów wypełniania ścieżki. Oznaczało to jednak też, że żadne elementy podrzędne postępu nie mogły znajdować się poza elementem. Inną wersję tego elementu postępu można utworzyć bez overflow: hidden. Może to stworzyć możliwości tworzenia animacji lub lepszych stanów ukończenia.

Gotowe

Selektory CSS wykonują tu ciężką pracę, porównując wartość maksymalną z wartością. Jeśli się zgadzają, proces jest zakończony. Po zakończeniu generowany jest pseudoelement, który jest dołączany na końcu elementu postępu, co stanowi dodatkową wizualną wskazówkę dla użytkownika.

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

Zrzut ekranu przedstawiający pasek ładowania na poziomie 100% z zaznacznikiem na końcu.

Kolor

Przeglądarka używa własnych kolorów dla elementu postępu i dostosowuje się do jasnych i ciemnych kolorów za pomocą tylko jednej właściwości CSS. Można to rozszerzyć o specjalne selektory dla poszczególnych przeglądarek.

Jasne i ciemne style przeglądarki

Aby włączyć w witrynie ciemny i jasny element <progress>, wystarczy użyć atrybutu color-scheme.

progress {
  color-scheme: light dark;
}

Kolor wypełnienia postępu pojedynczej usługi

Aby zastosować zabarwienie <progress>, użyj accent-color.

progress {
  accent-color: rebeccapurple;
}

Zwróć uwagę, że kolor tła ścieżki zmienia się z jasnego na ciemny w zależności od accent-color. Przeglądarka zapewnia odpowiedni kontrast: bardzo fajne.

Kolory jasne i ciemne w pełni dostosowane do potrzeb

Ustaw 2 właściwości niestandardowe elementu <progress>, jedną dla koloru ścieżki, a drugą dla koloru postępu ścieżki. W zapytaniu o media prefers-color-schemepodaj nowe wartości kolorów ścieżki i postępu ścieżki.

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

Style fokusu

Wcześniej przypisaliśmy temu elementowi ujemny indeks tabulacji, aby można go było automatycznie skoncentrować. Użyj:focus-visible, aby dostosować tryb pełnej koncentracji i wybrać inteligentniejszy styl pierścienia podświetlenia. W tym przypadku kliknięcie myszką i ustawienie kursora nie spowoduje wyświetlenia pierścienia, ale kliknięcia klawiaturą. Film w YouTube zawiera więcej informacji na ten temat i warto go obejrzeć.

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

Zrzut ekranu przedstawiający pasek wczytywania z pierścieniem regulacji ostrości. Wszystkie kolory są zgodne.

Style niestandardowe w różnych przeglądarkach

Dostosuj style, wybierając części elementu <progress>, które są dostępne w danym przeglądarce. Użycie elementu postępu to pojedynczy tag, ale składa się on z kilku elementów podrzędnych, które są wyświetlane za pomocą pseudoselektorów CSS. Jeśli włączysz to ustawienie, Narzędzia programistyczne Chrome wyświetlą te elementy:

  1. Kliknij prawym przyciskiem myszy stronę i wybierz Zbadaj element, aby otworzyć Narzędzia deweloperskie.
  2. Kliknij koło zębate ustawień w prawym górnym rogu okna Narzędzia deweloperskie.
  3. W nagłówku Elementy znajdź i zaznacz pole wyboru Pokaż DOM w cieni klienta użytkownika.

Zrzut ekranu pokazujący, gdzie w Narzędziach deweloperskich można włączyć wyświetlanie elementu shadow DOM klienta użytkownika.

Style w Safari i Chromium

Przeglądarki oparte na WebKit, takie jak Safari i Chromium, udostępniają funkcje ::-webkit-progress-bar i ::-webkit-progress-value, które umożliwiają używanie podzbioru CSS. Na razie ustaw background-color za pomocą właściwości niestandardowych utworzonych wcześniej, które dostosowują się do ciemnych i jasnych kolorów.

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

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

Zrzut ekranu przedstawiający elementy wewnętrzne elementu postępu.

Style w Firefoksie

Firefox udostępnia elementowi <progress> tylko pseudoselektor ::-moz-progress-bar. Oznacza to również, że nie możemy zmienić zabarwienia ścieżki bezpośrednio.

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

Zrzut ekranu z Firefoxem i miejscem, w którym znajdują się elementy elementu postępu.

Zrzut ekranu z obszarem Debugging Corner, w którym widoczny jest pasek wczytywania w przeglądarkach Safari, iOS Safari, Firefox, Chrome i Chrome na Androidzie.

Zwróć uwagę, że w Firefox kolor ścieżki jest ustawiony na accent-color, a w Safari na iOS na jasnoniebieski. To samo w trybie ciemnym: Firefox ma ciemny ślad, ale nie ma niestandardowego koloru, który ustawiliśmy. Działa to w przeglądarkach opartych na Webkit.

Animacja

Podczas korzystania z wbudowanych w przeglądarkę pseudoselektorów często mamy do czynienia z ograniczonym zestawem dozwolonych właściwości CSS.

Animacja wypełniania ścieżki

Dodanie przejścia do inline-size elementu postępu działa w przypadku Chromium, ale nie w przypadku Safari. Firefox nie używa też właściwości przejścia w swoim ::-moz-progress-bar.

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

Animowanie stanu :indeterminate

Tutaj mogę być nieco bardziej kreatywny, więc pokażę animację. Tworzony jest pseudoelement dla Chromium, a w przypadku wszystkich 3 przeglądarek stosowany jest gradient, który jest animowany w obrębie wszystkich przeglądarek.

Właściwości niestandardowe

Właściwości niestandardowe są przydatne w wielu sytuacjach, ale jedną z moich ulubionych funkcji jest po prostu nadawanie nazwy wartościom CSS, które wyglądają na magiczne. Poniżej znajdziesz dość skomplikowany obiekt linear-gradient, ale z ładną nazwą. Jej cel i przypadki użycia są jasne.

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

Właściwości niestandardowe pomogą też zachować „DRY” w kodzie, ponieważ nie możemy zgrupować razem tych selektorów właściwych dla danej przeglądarki.

klatki kluczowe,

Celem jest nieskończona animacja, która odtwarza się w obie strony. Początkowe i końcowe klatki kluczowe zostaną ustawione w CSS. Aby utworzyć animację, która wraca do punktu początkowego, wystarczy użyć jednej klatki kluczowej, tej środkowej na poziomie 50%.

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

Kierowanie na poszczególne przeglądarki

Nie każda przeglądarka umożliwia tworzenie pseudoelementów w samym elemencie <progress>lub animowanie paska postępu. Więcej przeglądarek obsługuje animację ścieżki niż pseudoelement, więc przekształcam pseudoelementy w animowane paski.

Pseudoelement Chromium

Chromium zezwala na użycie pseudoelementu ::after z pozycją, aby objąć element. Używane są nieokreślone właściwości niestandardowe, a animacja w przód i w tył działa bardzo dobrze.

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);
}
Pasek postępu w Safari

W przypadku Safari właściwości niestandardowe i animacja są stosowane do paska postępu pseudoelementu:

progress:indeterminate::-webkit-progress-bar {
  background: var(--_indeterminate-track);
  background-size: var(--_indeterminate-track-size);
  background-position: right; 
  animation: var(--_indeterminate-track-animation);
}
Pasek postępu przeglądarki Firefox

W przeglądarce Firefox właściwości niestandardowe i animacja są również stosowane do pseudoelementowego paska postępu:

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

JavaScript

Kod JavaScript odgrywa ważną rolę w przypadku elementu <progress>. Określa ona wartość wysyłaną do elementu i zapewnia, że w dokumencie jest wystarczająca ilość informacji dla czytników ekranu.

const state = {
  val: null
}

Demonstracja zawiera przyciski do sterowania postępem. Aktualizują one state.val, a następnie wywołują funkcję do aktualizowania DOM.

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

setProgress()

W tej funkcji odbywa się koordynacja UI/UX. Zacznij od utworzenia funkcji setProgress(). Nie są potrzebne żadne parametry, ponieważ ma on dostęp do obiektu state, elementu postępu i strefy <main>.

const setProgress = () => {
  
}

Ustawianie stanu wczytywania w strefie <main>

W zależności od tego, czy postępy są zakończone, powiązany element <main> wymaga zaktualizowania atrybutu aria-busy:

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

Wyczyść atrybuty, jeśli ilość wczytywania jest nieznana

Jeśli wartość jest nieznana lub nieskonfigurowana, null w tym przypadku usuń atrybuty valuearia-valuenow. Spowoduje to ustawienie wartości <progress> na nieokreśloną.

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

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

Rozwiązywanie problemów z matematyką dziesiętną w JavaScriptzie

Zachowuję domyślne maksimum postępu wynoszące 1, więc funkcje zwiększania i zmniejszania w wersji demonstracyjnej korzystają z wartości dziesiętnych. JavaScript i inne języki nie zawsze dobrze sobie radzą. Oto funkcja roundDecimals(), która przycina nadmiar wyników obliczeń:

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

Zaokrąglaj wartość, aby była widoczna i czytelna:

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

Ustawianie wartości dla czytników ekranu i stanu przeglądarki

Wartość jest używana w 3 miejscach w DOM:

  1. Atrybut value elementu <progress>.
  2. Atrybut aria-valuenow.
  3. Treść wewnętrzna tagu <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
}

Ustawienie fokusa na postępach

Po zaktualizowaniu wartości użytkownicy widzący zobaczą zmianę postępu, ale użytkownicy czytników nie otrzymają jeszcze powiadomienia o zmianie. Zaznacz element <progress>, a przeglądarka poinformuje o aktualizacji.

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

Zrzut ekranu aplikacji Voice Over na Macu, która odczytuje użytkownikowi postęp ładowania.

Podsumowanie

Teraz, gdy już wiesz, jak to zrobić, jak Ty to zrobisz? 🙂

Z pewnością wprowadzę kilka zmian, jeśli tylko będę mieć taką możliwość. Myślę, że można uporządkować obecny komponent i spróbować stworzyć nowy bez ograniczeń stylu pseudoklasy elementu <progress>. Warto się tego przyjrzeć!

Stosujmy różne podejścia i poznajmy sposoby budowania obecności w internecie.

Utwórz wersję demonstracyjną, wyślij mi linki, a ja dodam je do sekcji z remiksami społeczności.

Remiksy utworzone przez społeczność