Tworzenie komponentu paska wczytywania

Podstawowe informacje o tym, jak utworzyć pasek wczytywania z adaptacją kolorów i dostępnością za pomocą elementu <progress>.

W tym poście przedstawię pomysły na stworzenie paska wczytywania z adaptacją kolorów i elementem <progress>. Wypróbuj wersję demonstracyjną i wyświetl źródło.

Jasny i ciemny, nieokreślony, rosnący i w pełni ukończony.

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

Przegląd

<progress> dostarcza użytkownikom wizualne i dźwiękowe informacje o ukończeniu procesu. Takie wizualne informacje zwrotne są przydatne w takich sytuacjach, jak: postęp w wypełnianiu formularza, wyświetlanie informacji o pobieraniu lub przesyłaniu danych, a nawet pokazanie, że postęp jest nieznany, ale prace są nadal aktywne.

To wyzwanie GUI współpracowało z istniejącym elementem HTML <progress>, by zmniejszyć ułatwienia dostępu. Kolory i układy przesuwają granice możliwości dostosowywania wbudowanego elementu, aby zmodernizować komponent i lepiej dopasować go do systemów projektowania.

Jasne i ciemne karty w każdej przeglądarce przedstawiające przegląd ikony adaptacyjnej (od góry do dołu): Safari, Firefox, Chrome.
Prezentacja demonstracyjna widoczna w przeglądarkach Firefox, Safari, iOS Safari, Chrome i Android w trybie jasnym i ciemnym.

Markup

Zdecydowałem się uchwycić element <progress> obiektem <label>, więc mogę pominąć atrybuty konkretnych relacji na rzecz niejawnej relacji. Oznaczyłem też element nadrzędny zależny od stanu wczytywania, aby technologie czytników ekranu mogły przekazać te informacje użytkownikowi.

<progress></progress>

Jeśli nie ma parametru value, postęp działania elementu jest nieokreślony. Atrybut max przyjmuje domyślnie wartość 1, więc postęp mieści się w zakresie od 0 do 1. Na przykład ustawienie max na 100 spowodowałoby ustawienie zakresu na 0–100. Wolę trzymać się limitów 0 i 1, tłumacząc wartości postępu na 0,5 lub 50%.

Postęp dodany przez etykietę

W relacji niejawnej element postępu jest oznaczony etykietą podobną do tej:

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

W mojej demonstracji chcę dodać etykietę tylko dla czytników ekranu. W tym celu trzeba owinąć tekst etykiety w elemencie <span> i zastosować do niego pewne style, dzięki czemu będzie on widoczny poza ekranem:

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

Użyj tego towarzyszącego kodu 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 z narzędziami deweloperskimi, gdzie widać element dostępny tylko na ekranie.

Obszar, na którym zależy postęp wczytywania

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

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

Z JavaScriptu przełącz aria-busy na true na początku zadania i na false po jego zakończeniu.

Dodane atrybuty Aria

Choć domyślnie rolę elementu <progress> pełnią progressbar, ta rola została wyraźnie wskazana w przeglądarkach, które nie są jej pozbawione. Dodaję też atrybut indeterminate, aby bezpośrednio umieścić element w nieznanym stanie. Jest to łatwiejsze niż obserwacja elementu, bez ustawionego parametru value.

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

Użyj tabindex="-1", aby umożliwić zaznaczenie elementu postępu z poziomu JavaScriptu. Jest to ważne w przypadku technologii czytnika ekranu, ponieważ informacja o postępie będzie informować użytkownika o tym, jak daleko został osiągnięty zaktualizowany postęp.

Style

Element postępu jest nieco skomplikowany, jeśli chodzi o styl. Wbudowane elementy HTML mają specjalne ukryte części, które trudno wybrać. Często oferują tylko ograniczony zestaw właściwości, które można ustawić.

Układ

Style układu mają zapewniać pewną elastyczność w zakresie rozmiaru i pozycji etykiety elementu postępu. Dodajemy specjalny stan ukończenia, który może być przydatny, ale nie wymagany, dodatkowy wskazówka graficzna.

Układ: <progress>

Szerokość elementu postępu pozostaje niezmieniona, dzięki czemu może się zmniejszać i zwiększać odpowiednio do ilości potrzebnej przestrzeni. Style wbudowane są usuwane przez ustawienie appearance i border na none. Dzięki temu można je znormalizować w różnych przeglądarkach, ponieważ każda przeglądarka ma własny styl.

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 dla _radius wykorzystuje notację z liczbą naukową do wyrażenia dużej liczby, dzięki czemu border-radius jest zawsze zaokrąglana. Jest to odpowiednik 1000px. Używam tej wartości, ponieważ moim celem jest użycie na tyle dużej wartości, że mogę ją ustawić i ją zapomnieć (i jest krótsza niż w przypadku 1000px). W razie potrzeby łatwo jest ją zwiększyć – wystarczy zmienić 3 na 4, a 1e4px odpowiada wartości 10000px.

Język overflow: hidden jest używany i jego celem jest kontrowersyjny. Ułatwiono w ten sposób kilka usprawnień, takich jak brak konieczności przekazywania wartości border-radius do ścieżki i śledzenie elementów wypełnienia. Oznaczało to jednak, że żadne elementy podrzędne postępu nie mogą znajdować się poza tym elementem. Kolejną iterację tego niestandardowego elementu postępu można wykonać bez elementu overflow: hidden, co mogłoby stworzyć możliwości tworzenia animacji i lepszych stanów ukończenia.

Gotowe

Ciężką pracę wykonuje tu selektory arkusza CSS, porównując wartość maksymalną z wartością. Jeśli do siebie pasują, cały proces się kończy. Po zakończeniu generuje się pseudoelement, który jest dołączany na końcu elementu postępu, zapewniając piękną dodatkową wskazówkę, jak to zrobić.

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 pokazujący pasek ładowania ustawiony na 100% i znacznik wyboru na końcu.

Kolor

Przeglądarka ma własne kolory elementu postępu i może dostosować się do jasnego i ciemnego za pomocą jednej właściwości CSS. Można to wykorzystać przy użyciu specjalnych selektorów właściwych dla poszczególnych przeglądarek.

Jasny i ciemny styl przeglądarki

Aby włączyć w witrynie ciemny i jasny adaptacyjny element <progress>, wystarczy tylko color-scheme.

progress {
  color-scheme: light dark;
}

Kolor pola postępu w pojedynczej usłudze

Aby zabarwić element <progress>, użyj właściwości 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 parametru accent-color. Przeglądarka zapewnia odpowiedni kontrast: całkiem schludny.

Całkowicie niestandardowe jasne i ciemne kolory

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

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 ostrości

Wcześniej nadaliśmy elementowi ujemny indeks tabulacji, aby umożliwić jego automatyczne zaznaczenie. Użyj :focus-visible, aby dostosować ostrość i włączyć inteligentny styl pierścienia ostrości. Po kliknięciu i zaznaczeniu myszą nie będzie widać pierścienia ostrości, a kliknięcia na klawiaturze. Szczegółowo omawiamy to w filmie w YouTube, który zawiera omówienie tej kwestii.

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

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

Niestandardowe style w różnych przeglądarkach

Dostosuj style, wybierając części elementu <progress> widoczne dla każdej przeglądarki. Użycie elementu postępu to pojedynczy tag, ale składa się z kilku elementów podrzędnych, które są wyświetlane za pomocą pseudoselektorów CSS. Jeśli włączysz ustawienie, Narzędzia deweloperskie w Chrome pokażą Ci te elementy:

  1. Kliknij stronę prawym przyciskiem myszy i wybierz Zbadaj element, aby wyświetlić Narzędzia deweloperskie.
  2. Kliknij ikonę koła zębatego (Ustawienia) w prawym górnym rogu okna Narzędzi deweloperskich.
  3. Pod nagłówkiem Elementy znajdź i zaznacz pole wyboru Pokaż cień klienta użytkownika.

Zrzut ekranu pokazujący, gdzie w Narzędziach deweloperskich można udostępnić DOM klienta użytkownika.

Style Safari i Chromium

Przeglądarki oparte na WebKit, takie jak Safari i Chromium, ujawniają wartości ::-webkit-progress-bar i ::-webkit-progress-value, które umożliwiają używanie podzbioru CSS. Na razie ustaw background-color, korzystając z utworzonych wcześniej właściwości niestandardowych, które dostosowują się do jasności i ciemności.

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

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

Zrzut ekranu z wewnętrznymi elementami elementu postępu.

Style przeglądarki Firefox

Firefox wyświetla tylko pseudoselektor ::-moz-progress-bar w elemencie <progress>. Oznacza to również, że nie możemy zabarwić ścieżki bezpośrednio.

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

Zrzut ekranu przedstawiający przeglądarkę Firefox i lokalizację elementów postępu.

Zrzut ekranu przedstawiający sekcję Debugging Corner, w której widoczny jest pasek wczytywania w przeglądarkach Safari, Safari na iOS, Firefox, Chrome i Chrome na Androida.

Zwróć uwagę, że w przeglądarce Firefox jest ustawiony kolor ścieżki z accent-color, a w przeglądarce iOS – jasnoniebieską. Tak samo wygląda to w trybie ciemnym: w przeglądarce Firefox występuje ciemna ścieżka, ale nie ma ona ustawionego przez nas koloru niestandardowego. Działa w przeglądarkach opartych na pakiecie Webkit.

Animacja

Podczas pracy z wbudowanymi pseudoselektorami w przeglądarce często ma ona ograniczony zestaw dozwolonych właściwości CSS.

Animowanie ścieżki w trakcie zapełniania się ścieżki

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

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

Optymalizuję stan: :indeterminate

Teraz pokażę coś bardziej kreatywnego i animację. Zostanie utworzony pseudoelement dla Chromium i zastosowany gradient, który będzie animowany w obie strony we wszystkich trzech przeglądarkach.

Właściwości niestandardowe

Właściwości niestandardowe są przydatne w wielu przypadkach, ale jedną z moich ulubionych jest po prostu nazwanie wartości CSS, która wygląda inaczej. Poniższy fragment kodu linear-gradient jest dość skomplikowany, ale z ładną nazwą. Można ją łatwo zrozumieć oraz zrozumieć jej cel i przypadki użycia.

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 także pomogą w utrzymaniu stanu DRY, ponieważ nie możemy grupować selektorów specyficznych dla danej przeglądarki.

Klatki kluczowe

Celem jest niekończąca się animacja, która pojawia się w obie strony. Początkowe i końcowe klatki kluczowe będą ustawione w CSS. Potrzebna jest tylko 1 klatka kluczowa – środkowa klatka kluczowa w obszarze 50% – do utworzenia animacji powracającej do miejsca, w którym została zainicjowana, i w kółko.

@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 umożliwia animowanie paska postępu. Coraz więcej przeglądarek obsługuje animację ścieżki, a nie pseudoelement, więc zamiast pseudoelementów używam pseudoelementów jako podstawy do animacji pasków.

Pseudoelement Chromium

Chromium zezwala na pseudoelement: ::after używany z pozycją zasłonięcia elementu. Używane są nieokreślone właściwości niestandardowe, a animacje w przód i z powrotem działają 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 pseudoelementu paska postępu:

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ą też stosowane do pseudoelementu 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

JavaScript odgrywa ważną rolę w elemencie <progress>. Kontroluje on wartość wysyłaną do elementu i dba o to, aby dokument zawierał wystarczającą ilość informacji dla czytników ekranu.

const state = {
  val: null
}

W wersji demonstracyjnej znajdują się przyciski do kontrolowania postępu: aktualizują się one state.val, a następnie wywołują funkcję aktualizowania DOM.

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

setProgress()

To tu odbywa się administracja 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ęp został ukończony, powiązany element <main> wymaga aktualizacji atrybutu aria-busy:

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

Usuń atrybuty, jeśli ilość wczytywania jest nieznana

Jeśli wartość jest nieznana lub nieskonfigurowana, null w tym zastosowaniu usuń atrybuty value i aria-valuenow. Spowoduje to nieokreślenie stanu <progress>.

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 JavaScript

Ponieważ zdecydowałem się zostać przy domyślnym ustawieniu postępu wynoszącym 1, funkcje demonstracyjnego zwiększania i zmniejszania korzystają z matematyki dziesiętnej. JavaScript i inne języki nie zawsze się w tym sprawdzają. Oto funkcja roundDecimals(), która pozwala usunąć nadmiar z wyniku matematycznego:

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

Ustaw wartość czytników ekranu i stanu przeglądarki

Ta wartość jest używana w trzech miejscach w DOM:

  1. Atrybut value elementu <progress>.
  2. Atrybut aria-valuenow.
  3. Wewnętrzna zawartość tekstowa funkcji <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
}

Skupienie na postępach

Po zaktualizowaniu wartości widzący użytkownicy będą widzieć zmianę postępu, ale czytniki ekranu nie otrzymają jeszcze ogłoszenia o zmianie. Zaznacz element <progress>, a przeglądarka poinformuje Cię 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 w systemie macOS, który pokazuje użytkownikowi postęp paska wczytywania.

Podsumowanie

Wiesz już, jak to zrobiłem, więc jak to zrobisz 🙂

Na pewno chciałbym wprowadzić kilka zmian, jeśli mam jeszcze jedną szansę. Wydaje mi się, że wystarczy wyczyścić bieżący komponent i spróbować utworzyć go bez ograniczeń dotyczących pseudoklasy w elemencie <progress>. Warto sprawdzić to!

Stwórzmy różne metody i nauczmy się wszystkiego, jak rozwijać się w internecie.

Utwórz demonstrację i udostępnię mi linki na Twitterze, a dodam ją do sekcji remiksów w ramach społeczności poniżej.

Remiksy społeczności