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.
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.](https://web.dev/static/articles/building/a-loading-bar-component/image/light-dark-tabs-each-br-cd04389a437d1.png?authuser=7&hl=pl)
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;
}
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);
}
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;
}
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:
- Kliknij stronę prawym przyciskiem myszy i wybierz Zbadaj element, aby wyświetlić Narzędzia deweloperskie.
- Kliknij koło zębate ustawień w prawym górnym rogu okna Narzędzia deweloperskie.
- W sekcji Elementy znajdź i zaznacz pole wyboru Pokaż model 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);
}
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);
}
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:
- Atrybut
value
elementu<progress>
. - Atrybut
aria-valuenow
. - 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()
}
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ść.