Tworzenie komponentu paska wczytywania

Podstawowe informacje o tworzeniu dostępnego paska ładowania dostosowującego się do kolorów za pomocą elementu <progress>.

W tym poście chcę podzielić się przemyśleniami na temat tego, jak utworzyć dostępny pasek ładowania dostosowujący się do kolorów za pomocą elementu <progress>. Wypróbuj wersję demonstracyjną i wyświetl źródło.

Jasny i ciemny, nieokreślony, rosnący i ukończony – wersja demonstracyjna w Chrome.

Jeśli wolisz obejrzeć film, tutaj znajdziesz wersję tego posta na YouTube:

Przegląd

Element <progress> zapewnia użytkownikom wizualne i dźwiękowe informacje zwrotne o ukończeniu. Te wizualne informacje zwrotne są przydatne w takich sytuacjach jak: postęp w wypełnianiu formularza, wyświetlanie informacji o pobieraniu lub przesyłaniu, a nawet pokazywanie, że postęp jest nieznany, ale praca nadal trwa.

W tym wyzwaniu GUI wykorzystaliśmy istniejący element HTML <progress>, aby ułatwić sobie pracę nad ułatwieniami dostępu. Kolory i układy przesuwają granice dostosowywania wbudowanego elementu, aby go unowocześnić i lepiej dopasować do systemów projektowania.

Jasne i ciemne karty w każdej przeglądarce, które pokazują przegląd ikony adaptacyjnej od góry do dołu: Safari, Firefox, Chrome.
Wersja demonstracyjna wyświetlana w przeglądarkach Firefox, Safari, Safari na iOS, Chrome i Chrome na Androida w jasnym i ciemnym schemacie.

Znacznik

Zdecydowałem się umieścić element <progress> w elemencie <label>, aby pominąć atrybuty relacji jawnej na rzecz relacji niejawnej. Oznaczyłem też element nadrzędny, na który wpływa stan wczytywania, aby czytniki ekranu mogły przekazać tę informację użytkownikowi.

<progress></progress>

Jeśli nie ma elementu value, postęp elementu jest nieokreślony. Atrybut max ma domyślnie wartość 1, więc postęp mieści się w zakresie od 0 do 1. Jeśli na przykład ustawisz max na 100, zakres będzie wynosił 0–100. Zdecydowałem się pozostać w zakresie od 0 do 1, przekształcając wartości postępu na 0,5, czyli 50%.

Postęp z etykietą

W przypadku relacji domniemanej element postępu jest opakowany etykietą w ten sposób:

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

W mojej wersji demonstracyjnej wybrałem opcję uwzględnienia etykiety tylko dla czytników ekranu. W tym celu umieść tekst etykiety w elemencie <span> i zastosuj do niego style, które sprawią, że nie będzie on widoczny na ekranie:

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

Z następującym kodem CSS z witryny 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 dla programistów pokazujący element screen-ready-only.

Obszar, na który ma wpływ postęp wczytywania

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

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

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

Dodanie atrybutów ARIA

Chociaż domyślną rolą elementu <progress> jest progressbar, określiłem ją bezpośrednio dla przeglądarek, które nie mają tej domyślnej roli. Dodaliśmy też atrybut indeterminate, aby wyraźnie ustawić element w stanie nieznanym, co jest bardziej przejrzyste 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 tabindex="-1", aby element postępu można było zaznaczyć za pomocą JavaScriptu. Jest to ważne w przypadku czytników ekranu, ponieważ ustawienie fokusu na postępie podczas jego zmiany spowoduje, że użytkownik usłyszy, jak daleko postęp się zmienił.

Style

Element postępu jest nieco trudny do stylizowania. 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. Dodawany jest specjalny stan ukończenia, który może być przydatną, ale nie wymaganą, dodatkową wskazówką wizualną.

Układ: <progress>

Szerokość elementu postępu pozostaje niezmieniona, dzięki czemu może się on zmniejszać i zwiększać w zależności od miejsca potrzebnego w projekcie. Wbudowane style są usuwane przez ustawienie właściwości appearance i border na none. Dzięki temu element można znormalizować 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 dla _radius używa notacji naukowej do wyrażenia dużej liczby, więc border-radius jest zawsze zaokrąglana. Jest to odpowiednik 1000px. Używam tej wartości, ponieważ moim celem jest użycie wartości na tyle dużej, aby można było ją ustawić i o niej zapomnieć (a jej zapisanie jest krótsze niż 1000px). W razie potrzeby można ją łatwo zwiększyć: wystarczy zmienić 3 na 4, a wtedy 1e4px będzie równoważne 10000px.

Używany jest styl overflow: hidden, który budzi kontrowersje. Ułatwiło to kilka rzeczy, np. nie trzeba było przekazywać wartości border-radius do elementów ścieżki i wypełnienia ścieżki, ale oznaczało to też, że żadne elementy podrzędne paska postępu nie mogły znajdować się poza elementem. Kolejną iterację tego niestandardowego elementu postępu można wykonać bez overflow: hidden, co może otworzyć pewne możliwości w zakresie animacji lub lepszych stanów ukończenia.

Gotowe

Selektory CSS wykonują tu trudną pracę, porównując wartość maksymalną z wartością, a jeśli są zgodne, postęp jest zakończony. Po zakończeniu generowany jest pseudoelement, który jest dołączany na końcu elementu postępu, co stanowi dodatkową wskazówkę wizualną dotyczącą zakończenia.

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% i ikonę potwierdzenia na końcu.

Kolor

Przeglądarka ma własne kolory elementu postępu i dostosowuje się do jasnego i ciemnego motywu za pomocą tylko jednej właściwości CSS. Można to rozbudować za pomocą specjalnych selektorów dla przeglądarek.

Jasne i ciemne style przeglądarki

Aby włączyć w witrynie element <progress> dostosowujący się do trybu ciemnego i jasnego, wystarczy dodać color-scheme.

progress {
  color-scheme: light dark;
}

Kolor wypełnienia postępu pojedynczej właściwości

Aby zabarwić element <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 dba o odpowiedni kontrast. To świetne rozwiązanie.

W pełni niestandardowe jasne i ciemne kolory

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-scheme podaj 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 zaznaczenia

Wcześniej nadaliśmy elementowi ujemny indeks tabulacji, aby można było go zaznaczyć programowo. Użyj :focus-visible, aby dostosować zaznaczenie i włączyć inteligentniejszy styl pierścienia zaznaczenia. Dzięki temu kliknięcie myszą i zaznaczenie nie spowodują wyświetlenia pierścienia zaznaczenia, ale kliknięcia klawiatury już tak. Więcej informacji na ten temat znajdziesz w filmie na YouTube.

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

Zrzut ekranu przedstawiający pasek wczytywania z pierścieniem zaznaczenia. Kolory są dopasowane.

Style niestandardowe w różnych przeglądarkach

Dostosuj style, wybierając części elementu <progress>, które są widoczne w każdej przeglądarce. Element postępu to pojedynczy tag, ale składa się z kilku elementów podrzędnych, które są widoczne dzięki pseudoselektorom CSS. Narzędzia deweloperskie w Chrome będą wyświetlać te elementy, jeśli włączysz ustawienie:

  1. Kliknij prawym przyciskiem myszy stronę i wybierz Zbadaj, aby otworzyć Narzędzia deweloperskie.
  2. W prawym górnym rogu okna Narzędzi deweloperskich kliknij ikonę koła zębatego Ustawienia.
  3. W sekcji Elementy znajdź i zaznacz pole wyboru Pokaż shadow DOM klienta użytkownika.

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

Style Safari i Chromium

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

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

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

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

Style Firefoksa

Firefox udostępnia pseudoselektor ::-moz-progress-bar tylko w elemencie <progress>. Oznacza to też, że nie możemy bezpośrednio zabarwić ścieżki.

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

Zrzut ekranu przeglądarki Firefox i miejsca, w którym można znaleźć części elementu postępu.

Zrzut ekranu przedstawiający kącik debugowania, w którym widać, że pasek wczytywania działa w przeglądarkach Safari, Safari na iOS, Firefox, Chrome i Chrome na Androida.

Zwróć uwagę, że Firefox ma kolor ścieżki ustawiony na accent-color, a iOS Safari ma jasnoniebieską ścieżkę. Tak samo jest w trybie ciemnym: Firefox ma ciemny pasek, ale nie ma ustawionych przez nas spersonalizowanych kolorów. Działa to w przeglądarkach opartych na Webkit.

Animacja

Podczas pracy z wbudowanymi pseudoselektorami przeglądarki 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 Chromium, ale nie w Safari. Firefox nie używa też właściwości przejścia w ::-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ę wykazać się większą kreatywnością i dodać animację. Tworzony jest pseudoelement dla Chromium i stosowany jest gradient, który jest animowany w przód i w tył we wszystkich 3 przeglądarkach.

Właściwości niestandardowe

Właściwości niestandardowe mają wiele zastosowań, ale jednym z moich ulubionych jest po prostu nadawanie nazwy wartości CSS, która w przeciwnym razie wyglądałaby jak magiczna. Poniżej znajduje się dość złożona linear-gradient, ale o ładnej nazwie. Cel i przypadki użycia są zrozumiałe.

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ć kod DRY, ponieważ nie możemy grupować tych selektorów związanych z przeglądarką.

klatki kluczowe,

Celem jest nieskończona animacja, która odtwarza się w pętli. Klatki kluczowe początku i końca zostaną ustawione w CSS. Do utworzenia animacji, która będzie się powtarzać w nieskończoność, wystarczy tylko jedna klatka kluczowa – środkowa klatka kluczowa w 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> ani animowanie paska postępu. Więcej przeglądarek obsługuje animowanie ścieżki niż pseudoelementu, więc przechodzę od pseudoelementów jako podstawy do animowania pasków.

Pseudoelement Chromium

Chromium zezwala na pseudoelement ::after używany z pozycją, aby zakryć element. Używane są właściwości niestandardowe stanu nieokreślonego, a animacja 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 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 w Firefoksie

W przypadku przeglądarki Firefox właściwości niestandardowe i animacja są również 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 przypadku elementu <progress>. Kontroluje wartość wysyłaną do elementu i zapewnia, że w dokumencie jest wystarczająco dużo informacji dla czytników ekranu.

const state = {
  val: null
}

Wersja demonstracyjna zawiera przyciski do kontrolowania postępu. Aktualizują one state.val, a następnie wywołują funkcję aktualizowania DOM.

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

setProgress()

W tej funkcji odbywa się aranżacja UI/UX. Zacznij od utworzenia funkcji setProgress(). Parametry nie są potrzebne, ponieważ funkcja ma dostęp do obiektu state, elementu postępu i strefy <main>.

const setProgress = () => {
  
}

Ustawiam stan wczytywania w strefie <main>

W zależności od tego, czy postęp jest zakończony, czy nie, powiązany element <main> wymaga aktualizacji atrybutu aria-busy:

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

Wyczyść atrybuty, jeśli ilość obciążenia jest nieznana

Jeśli wartość jest nieznana lub nie została ustawiona, null w tym przypadku usuń atrybuty value i aria-valuenow. Spowoduje to zmianę stanu <progress> na nieokreślony.

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 obliczeniami dziesiętnymi w JavaScripcie

Ponieważ zdecydowałem się zachować domyślną maksymalną wartość postępu wynoszącą 1, funkcje zwiększania i zmniejszania w wersji demonstracyjnej używają matematyki dziesiętnej. JavaScript i inne języki nie zawsze dobrze sobie z tym radzą. Oto funkcja roundDecimals(), która przytnie nadmiarowe dane z wyniku matematycznego:

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

Zaokrąglij wartość, aby była czytelna i można ją było wyświetlić:

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ść tekstu wewnętrznego <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
}

Ustawianie fokusu na postępie

Po zaktualizowaniu wartości użytkownicy widzący zobaczą zmianę postępu, ale użytkownicy czytników ekranu nie otrzymają jeszcze powiadomienia o zmianie. Ustaw fokus na elemencie <progress>, a przeglądarka ogłosi aktualizację.

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 VoiceOver na Mac OS odczytującej użytkownikowi postęp na pasku ładowania.

Podsumowanie

Teraz, gdy już wiesz, jak to zrobiłem, jak Ty byś to zrobił? 🙂

Jeśli miałbym jeszcze jedną szansę, na pewno wprowadziłbym kilka zmian. Myślę, że można ulepszyć obecny komponent i spróbować utworzyć taki, który nie będzie miał ograniczeń stylu pseudoklasy elementu <progress>. Warto się tym zainteresować.

Zróżnicujmy nasze podejście i poznajmy wszystkie sposoby tworzenia stron internetowych.

Utwórz wersję demonstracyjną, wyślij mi tweeta z linkami, a ja dodam ją do sekcji remiksów społeczności poniżej.

Remiksy społeczności