Tworzenie komponentu paska wczytywania

Podstawowe omówienie sposobu tworzenia kolorowego, adaptacyjnego i łatwo dostępnego paska wczytywania za pomocą elementu <progress>.

W tym poście opowiem, jak stworzyć kolorowy adaptacyjny i łatwo dostępny pasek wczytywania z elementem <progress>. Wypróbuj wersję demonstracyjną i zobacz źródło.

W Chrome prezentowano jasne i ciemne elementy, nieokreślone, rosnące i pełne.

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

Przegląd

Element <progress> dostarcza użytkownikom wizualne i dźwiękowe informacje o ukończeniu projektu. Jest on przydatny podczas realizacji takich scenariuszy jak wypełnienie formularza, wyświetlanie informacji o pobieraniu lub przesyłaniu, a nawet pokazywanie, że postęp jest nieznany, a praca jest nadal aktywna.

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 przekraczają granice możliwości dostosowania wbudowanego elementu, co pozwala zmodernizować komponent i lepiej dopasować go 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.

Markup

Postanowiłem umieścić element <progress> w elemencie <label>, więc mogłem pominąć atrybuty jawne relacji na rzecz relacji niejawnej. Oznaczyłem też element nadrzędny, na który wpływa stan wczytywania, dzięki czemu technologie czytników ekranu mogą przekazać te informacje użytkownikowi.

<progress></progress>

Jeśli brak value, postęp elementu jest nieokreślony. Atrybut max przyjmuje domyślnie wartość 1, więc postęp mieści się w zakresie od 0 do 1. Ustawienie max na przykład na 100 powoduje ustawienie zakresu od 0 do 100. Zdecydowałam się zmieścić w granicach od 0 do 1, przekładając wartości postępu na 0,5 lub 50%.

Postęp dodawania etykiet

W relacji niejawnej element postępu jest opakowany w taki sposób:

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

W mojej wersji demonstracyjnej zdecydowałem się umieścić etykietę tylko dla czytników ekranu. Można to zrobić, umieszczając tekst etykiety w elemencie <span> i stosując do niego kilka stylów, aby był on niewidoczny na ekranie:

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

Za pomocą tej usługi porównywania cen 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 przedstawiający narzędzia deweloperskie z elementem tylko na ekranie.

Obszar, na który wpływa 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 poprawić ten błąd, przypisz atrybut aria-busy do elementu, który znajduje się najwyżej na stronie, a zmieni się on po zakończeniu wczytywania. Dodatkowo wskaż zależność między postępem a strefą wczytywania za pomocą narzędzia aria-describedby.

<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, a na false po jego zakończeniu.

Dodania atrybutów Aria

Chociaż domniemaną rolą elementu <progress> jest progressbar, wyraźnie zaznaczyłam ją w przypadku przeglądarek, które nie mają tej roli domyślnej. Dodałem też atrybut indeterminate, aby jawnie nadać elementowi stan „nieznany”, czyli wyraźniejszy niż obserwowanie elementu bez ustawionego value.

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

Aby zaznaczyć element postępu w JavaScripcie, użyj właściwości tabindex="-1". Jest to ważne w przypadku technologii czytnika ekranu, ponieważ ukierunkowanie na postęp w miarę ich zmian informuje użytkownika o tym, jak daleko udało się zajść w ramach aktualizacji.

Style

Element postępu nie jest łatwy, jeśli chodzi o stylizację. Wbudowane elementy HTML zawierają specjalne ukryte elementy, które mogą być trudne do wybrania. Często mają też ograniczony zestaw właściwości.

Układ

Style układu mają zapewniać pewną elastyczność w zakresie rozmiaru i położenia etykiety elementu postępu. Dodawany jest specjalny stan ukończenia, który może być przydatnym, ale niewymaganym, dodatkowym elementem wizualnym.

Układ <progress>

Szerokość elementu postępu pozostaje niezmieniona, dzięki czemu może się zmniejszać i zwiększać wraz z ilością potrzebnego miejsca w projekcie. Style wbudowane zostaną usunięte przez ustawienie appearance i border na none. Dzięki temu element może zostać znormalizowany w różnych przeglądarkach, ponieważ każda przeglądarka ma własne style 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. Jest to odpowiednik 1000px. Używam tej wartości, bo używam na tyle dużej wartości, że mogę ją ustawić i ją zapomnieć (która jest krótsza do zapisu niż 1000px). Możesz też łatwo powiększyć w razie potrzeby wymiar jeszcze większy: po prostu zmień 3 na 4, a 1e4px odpowiada 10000px.

Styl overflow: hidden jest używany i jest spornym. Ułatwiło to kilka spraw, na przykład nie trzeba było przekazywać wartości border-radius do ścieżki czy śledzić elementy wypełnienia. Jednocześnie oznaczało, że nie można było śledzić postępów poza elementem. Kolejną iterację tego niestandardowego elementu postępu można było wykonać bez overflow: hidden, co mogłoby stwarzać możliwości tworzenia animacji lub lepszego stanu ukończenia.

Gotowe

Selektory CSS wykonują tu najtrudniejszą pracę, porównując wartość maksymalną z wartością. Jeśli się zgadzają, kończy się proces. Gdy to zrobisz, zostanie wygenerowany pseudoelement, który zostanie dołączony na końcu elementu postępu, co stanowi dodatkowy wizualny sygnał potwierdzający zakończenie.

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 wczytywania w 100% i znacznik wyboru na końcu.

Kolor

Przeglądarka nadaje elementowi postępu w innych kolorach, a dzięki jednej właściwości CSS zmienia się w jasne i ciemne kolory. Można to wykorzystać za pomocą specjalnych selektorów dla danej przeglądarki.

Jasne i ciemne style przeglądarki

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

progress {
  color-scheme: light dark;
}

Kolor wypełnienia pojedynczej usługi

Aby zmienić odcień elementu <progress>, użyj accent-color.

progress {
  accent-color: rebeccapurple;
}

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

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 dla ścieżki i śledź postęp.

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

Zaznacz style

Wcześniej przypisaliśmy temu elementowi ujemny indeks tabulacji, aby można go było automatycznie skoncentrować. Użyj :focus-visible, aby dostosować ostrość i włączyć bardziej inteligentniejszy pierścień ostrości. Dzięki temu pierścień zaznaczenia nie pojawi się po kliknięciu i zaznaczeniu za pomocą klawiatury. Tę kwestię znajdziesz bardziej szczegółowo w filmie w YouTube. Warto się z nimi zapoznać.

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

Zrzut ekranu przedstawiający pasek wczytywania z pierścieniem regulacji ostrości. Wszystkie kolory się zgadzają.

Style niestandardowe w różnych przeglądarkach

Dostosuj style, wybierając części elementu <progress> wyświetlane przez każdą przeglądarkę. Użycie elementu postępu to pojedynczy tag, który składa się z kilku elementów podrzędnych, które są wyświetlane za pomocą pseudoselektorów CSS. Jeśli włączysz to ustawienie, w Narzędziach deweloperskich w Chrome będą widoczne te elementy:

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

Zrzut ekranu pokazujący miejsce w Narzędziach deweloperskich, które umożliwia ujawnienie modelu shadow DOM klienta użytkownika.

Style w Safari i Chromium

Przeglądarki oparte na WebKit, takie jak Safari i Chromium, ujawniają zasoby ::-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 oświetlenia 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 przedstawiający wewnętrzne elementy elementu postępu.

Style w przeglądarce Firefox

Firefox wyświetla pseudoselektor ::-moz-progress-bar tylko w elemencie <progress>. 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 przeglądarki Firefox z informacją, gdzie można znaleźć elementy dotyczące 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 kolor ścieżki w przeglądarce Firefox został ustawiony na accent-color, a w przeglądarce iOS – kolor jasnoniebieską. Tak samo jest w trybie ciemnym: w Firefoksie jest wyświetlana ciemna ścieżka, ale nie ustawiony przez nas kolor niestandardowy. Funkcja działa też w przeglądarkach opartych na technologii Webkit.

Animacja

Podczas pracy z wbudowanymi pseudoselektorami w przeglądarce często używa się ograniczonego zestawu 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 również nie używa właściwości przejścia na stronie ::-moz-progress-bar.

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

Animowanie stanu :indeterminate

Teraz zrobię się nieco bardziej kreatywnie, więc mogę utworzyć animację. Zostanie utworzony pseudoelement dla Chromium i zastosowany gradient, który będzie animowany w tę i z powrotem we wszystkich 3 przeglądarkach.

Właściwości niestandardowe

Właściwości niestandardowe świetnie nadają się do wielu rzeczy, ale jedną z moich ulubionych jest nadawanie nazw magicznym magicznym wartościom CSS. 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 uzyskanie nieskończonej animacji, która pojawia się w tę i z powrotem. Początkowe i końcowe klatki kluczowe zostaną ustawione w CSS. Aby utworzyć animację, która powraca do miejsca, w którym się rozpoczęła, potrzebna jest tylko jedna klatka kluczowa (środkowa klatka kluczowa w 50%).

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

Kierowanie na każdą przeglądarkę

Nie każda przeglądarka umożliwia tworzenie pseudoelementów w samym elemencie <progress> lub nie pozwala na animowanie paska postępu. Więcej przeglądarek obsługuje animowanie ścieżki niż pseudoelementowy element, więc zmieniam pseudoelementy jako podstawę na animowane paski.

Pseudoelement Chrome

Chromium zezwala na używanie pseudoelementu ::after używanego z pozycją zasłaniającą element. Użyto tu nieokreślonych właściwości niestandardowych, a animacja „odwrotnie” i „do przodu” 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 przeglądarce Safari właściwości niestandardowe i animacja są stosowane na pseudoelementowym pasku 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ą 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

JavaScript odgrywa ważną rolę z elementem <progress>. Kontroluje wartość wysyłaną do elementu i zapewnia, że w dokumencie znajduje się wystarczająca ilość informacji dla czytników ekranu.

const state = {
  val: null
}

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

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

setProgress()

Ta funkcja służy do administrowania UI/UX. Zacznij od utworzenia funkcji setProgress(). Parametry nie są potrzebne, ponieważ ma on 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 został ukoń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ść wczytywania jest nieznana

Jeśli wartość jest nieznana lub nieskonfigurowana, w przypadku tego zastosowania null 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 matematyką liczb dziesiętnych w JavaScripcie

Zachowuję domyślne maksimum postępu wynoszące 1, więc funkcje zwiększania i zmniejszania wyników w wersji demonstracyjnej używają liczb dziesiętnych. JavaScript i inne języki nie zawsze dobrze sobie radzą. Oto funkcja roundDecimals(), która usunie nadmiarowy wynik 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

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

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

Koncentracja na postępach

Po zaktualizowaniu wartości pełnoletni użytkownicy zobaczą zmianę postępu, ale użytkownicy czytników ekranu nie otrzymają jeszcze informacji 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 z aplikacją Voice Over w systemie macOS czytającą użytkownikowi postęp paska wczytywania.

Podsumowanie

Wiesz już, jak to zrobiłem. Jak Ty? 🙂

Z pewnością chcę wprowadzić kilka zmian, jeśli mam jeszcze szansę. Myślę, że jest miejsce na uporządkowanie bieżącego komponentu i stworzenie takiego elementu bez ograniczeń pseudoklasowych elementów <progress>. Warto się tego przyjrzeć!

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

Przygotuj wersję demonstracyjną, a potem dodam linki do tweetów, a ja dodam ją do poniższej sekcji na temat remiksów na karcie Społeczność.

Remiksy utworzone przez społeczność