Podstawowe informacje o tym, jak utworzyć pasek ładowania z adaptacyjnym kolorem i ułatwieniami dostępu za pomocą elementu <progress>
.
W tym poście chcę podzielić się przemyśleniami na temat tworzenia paska ładowania, który dostosowuje się do koloru i jest dostępny, za pomocą elementu <progress>
. Wypróbuj
wersję demonstracyjną i wyświetl
kod źródłowy.
Jeśli wolisz film, obejrzyj tę wersję posta w YouTube:
Przegląd
Element
<progress>
informuje użytkowników wizualnie i dźwiękowo o zakończeniu działania. Ta informacja wizualna jest przydatna w przypadku takich scenariuszy jak postęp w wypełnianiu formularza, wyświetlanie informacji o pobieraniu lub przesyłaniu czy nawet pokazywanie, że postęp jest nieznany, ale praca nadal trwa.
Ten GUI Challenge wykorzystywał istniejący element HTML <progress>
, aby zaoszczędzić trochę pracy związanej z ułatwieniami dostępu. Kolory i układy przesuwają granice dostosowywania wbudowanego elementu, aby zmodernizować komponent i lepiej dopasować go do systemów projektowania.

Znacznik
Zdecydowałem się umieścić element <progress>
w <label>
, aby pominąć atrybuty relacji jawnej na rzecz relacji niejawnej.
Oznaczyłem też element nadrzędny, na który wpływa stan ładowania, aby czytniki ekranu mogły przekazywać te informacje 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 przedziale od 0 do 1. Ustawienie wartości max
na 100 spowoduje na przykład ustawienie zakresu na 0–100. Postanowiłem pozostać w zakresie od 0 do 1, przekształcając wartości postępu na 0,5 lub 50%.
Postęp oznaczania etykietami
W relacji domyślnej element postępu jest otoczony etykietą w ten sposób:
<label>Loading progress<progress></progress></label>
W wersji demonstracyjnej wybrałem opcję uwzględnienia etykiety tylko dla czytników ekranu.
W tym celu tekst etykiety jest umieszczany w tagu <span>
i stosowane są do niego style, które sprawiają, że jest on niewidoczny na ekranie:
<label>
<span class="sr-only">Loading progress</span>
<progress></progress>
</label>
Z tym kodem 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;
}
Obszar, którego dotyczy postęp wczytywania
Jeśli masz dobry wzrok, łatwo Ci będzie powiązać wskaźnik postępu z powiązanymi elementami i obszarami strony, ale dla użytkowników z wadami wzroku nie jest to takie oczywiste. Możesz to poprawić, przypisując atrybut
aria-busy
do elementu znajdującego się najwyżej, który zmieni się po zakończeniu wczytywania.
Wskaż też związek między postępem a strefą ładowania za pomocą symbolu aria-describedby
.
<main id="loading-zone" aria-busy="true">
…
<progress aria-describedby="loading-zone"></progress>
</main>
W JavaScript na początku zadania przełącz stan z aria-busy
na true
, a po jego zakończeniu na false
.
Dodanie atrybutów ARIA
Domyślna rola elementu <progress>
to progressbar
, ale określiłem ją jawnie, aby była dostępna w przeglądarkach, które nie mają tej domyślnej roli. Dodałem też atrybut
indeterminate
, aby wyraźnie określić stan elementu jako nieznany, 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 był dostępny dla JavaScriptu. Jest to ważne w przypadku technologii czytników ekranu, ponieważ przekazywanie fokusu postępu w miarę jego zmian informuje użytkownika, jak daleko postęp został zaktualizowany.
Style
Element postępu jest nieco trudny do ostylowania. 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ą zapewniać pewną elastyczność w zakresie rozmiaru elementu progress i położenia etykiety. Dodawany jest specjalny stan ukończenia, który może być przydatnym, ale nie wymaganym dodatkowym sygnałem wizualnym.
<progress>
Układ
Szerokość elementu postępu pozostaje bez zmian, dzięki czemu może się on zmniejszać i zwiększać w zależności od potrzebnej przestrzeni w projekcie. Wbudowane style są usuwane przez ustawienie wartości appearance
i border
na none
. Dzięki temu element może być znormalizowany w różnych przeglądarkach, ponieważ każda z nich 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
jest wyrażana w notacji naukowej, aby przedstawić dużą liczbę, więc border-radius
jest zawsze zaokrąglana. Jest to odpowiednik 1000px
. Lubię używać tej wartości, ponieważ moim celem jest użycie wartości wystarczająco dużej, aby można było ją ustawić i o niej zapomnieć (a jest krótsza niż 1000px
). W razie potrzeby można ją też łatwo zwiększyć: wystarczy zmienić 3 na 4, wtedy 1e4px
będzie równoważne 10000px
.
overflow: hidden
jest używany i budzi kontrowersje. Ułatwiło to kilka rzeczy, np. nie trzeba było przekazywać wartości border-radius
do ścieżki i elementów wypełniających ścieżkę, ale oznaczało też, że żaden element podrzędny elementu postępu nie mógł znajdować się poza nim. Kolejną iterację tego niestandardowego elementu postępu można przeprowadzić bez overflow: hidden
, co może otworzyć pewne możliwości 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ą one 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 dodatkowy sygnał wizualny o zakończeniu.
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 ma własne kolory elementu postępu i dostosowuje się do trybu jasnego i ciemnego za pomocą tylko jednej właściwości CSS. Można to rozbudować za pomocą specjalnych selektorów specyficznych dla przeglądarki.
Jasny i ciemny styl przeglądarki
Aby włączyć w witrynie element adaptacyjny <progress>
w wersji jasnej i ciemnej, wystarczy dodać atrybut color-scheme
.
progress {
color-scheme: light dark;
}
Kolor wypełnienia postępu pojedynczej usługi
Aby przyciemnić 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 zapewnia odpowiedni kontrast, co jest całkiem przydatne.
W pełni dostosowane kolory jasne i ciemne
Ustaw 2 właściwości niestandardowe w elemencie <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 programowo ustawić na nim fokus. Użyj :focus-visible
, aby dostosować ostrość i włączyć inteligentny pierścień ostrości. W tym przypadku kliknięcie myszą i zaznaczenie nie spowodują wyświetlenia pierścienia zaznaczenia, ale kliknięcia klawiatury tak. Więcej informacji znajdziesz w tym filmie w YouTube. Warto go obejrzeć.
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>
, które są widoczne w poszczególnych przeglądarkach. Element progress to pojedynczy tag, ale składa się z kilku elementów podrzędnych, które są udostępniane za pomocą pseudoselektorów CSS. Narzędzia deweloperskie w Chrome
wyświetlą te elementy, jeśli włączysz to ustawienie:
- Kliknij prawym przyciskiem myszy stronę i wybierz Zbadaj, aby otworzyć Narzędzia deweloperskie.
- W prawym górnym rogu okna Narzędzi deweloperskich kliknij koło zębate Ustawienia.
- W sekcji Elementy znajdź i zaznacz pole wyboru Pokaż cień klienta użytkownika w DOM.
Style Safari i Chromium
Przeglądarki oparte na WebKit, takie jak Safari i Chromium, udostępniają elementy ::-webkit-progress-bar
i ::-webkit-progress-value
, które umożliwiają używanie podzbioru CSS. Na razie ustaw background-color
za pomocą 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);
}
Style Firefoksa
Firefox udostępnia pseudoselektor ::-moz-progress-bar
tylko w przypadku elementu <progress>
. Oznacza to również, że nie możemy bezpośrednio przyciemnić ścieżki.
/* Firefox */
progress[value]::-moz-progress-bar {
background-color: var(--_progress);
}
Zwróć uwagę, że w przeglądarce Firefox kolor ścieżki jest ustawiony na accent-color
, a w iOS Safari ścieżka jest jasnoniebieska. W trybie ciemnym jest tak samo: Firefox ma ciemny pasek, ale nie ma ustawionego przez nas niestandardowego koloru, a w przeglądarkach opartych na WebKit działa.
Animacja
Podczas pracy z wbudowanymi w przeglądarkę pseudoselektorami często używa się ograniczonego zestawu dozwolonych właściwości CSS.
Animowanie wypełniania ścieżki
Dodanie przejścia do elementu 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 w przypadku elementu ::-moz-progress-bar
.
/* Chromium Only 😢 */
progress[value]::-webkit-progress-value {
background-color: var(--_progress);
transition: inline-size .25s ease-out;
}
Animowanie stanu :indeterminate
W tym miejscu mogę wykazać się większą kreatywnością i dodać animację. Dla Chromium tworzony jest pseudoelement 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 są przydatne w wielu sytuacjach, ale jedną z moich ulubionych jest po prostu nadawanie nazwy wartości CSS, która w innym przypadku wyglądałaby magicznie. Poniżej znajdziesz dość
złożony
linear-gradient
, ale z ładną nazwą. jego 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ć zasadę DRY, ponieważ nie możemy ponownie zgrupować tych selektorów specyficznych dla przeglądarki.
klatki kluczowe,
Celem jest nieskończona animacja, która porusza się w przód i w tył. Kluczowe klatki początkowe i końcowe zostaną ustawione w CSS. Aby utworzyć animację, która wielokrotnie wraca do punktu wyjścia, wystarczy tylko jedna klatka kluczowa – środkowa klatka kluczowa w punkcie 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ę z pseudoelementów jako podstawy na animowanie pasków.
Pseudoelement Chromium
Chromium zezwala na używanie pseudoelementu ::after
z pozycją, która obejmuje element. Używane są właściwości niestandardowe o nieokreślonej wartości, 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 pseudo-elementu:
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 Firefoxie
W przypadku przeglądarki Firefox właściwości niestandardowe i animacja są też stosowane do paska postępu pseudo-elementu:
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>
. Określa ona wartość wysyłaną do elementu i zapewnia, że w dokumencie jest wystarczająco dużo informacji dla czytników ekranu.
const state = {
val: null
}
W wersji demonstracyjnej znajdują się przyciski sterujące postępem. Aktualizują one state.val
, a następnie wywołują funkcję aktualizującą DOM.
document.querySelector('#complete').addEventListener('click', e => {
state.val = 1
setProgress()
})
setProgress()
W tej funkcji odbywa się aranżacja interfejsu. Zacznij od utworzenia funkcjisetProgress()
. Nie są potrzebne żadne parametry, ponieważ ma 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ł zakończony, powiązany element <main>
musi zostać zaktualizowany w atrybucie
aria-busy
:
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
}
Wyczyść atrybuty, jeśli kwota doładowania jest nieznana
Jeśli wartość jest nieznana lub nieskonfigurowana, null
w tym przypadku usuń atrybuty value
i aria-valuenow
. Spowoduje to przejście <progress>
w stan 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ą dziesiętną w JavaScript
Ponieważ zdecydowałem się pozostać przy domyślnej maksymalnej wartości postępu wynoszącej 1, funkcje zwiększania i zmniejszania w wersji demonstracyjnej korzystają z matematyki dziesiętnej. JavaScript i inne języki nie zawsze są w tym dobre.
Oto funkcja roundDecimals()
, która usunie nadmiarowe znaki z wyniku działania matematycznego:
const roundDecimals = (val, places) =>
+(Math.round(val + "e+" + places) + "e-" + places)
Zaokrąglij wartość, aby można ją było wyświetlić i odczytać:
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:
- Atrybut
value
elementu<progress>
. - Atrybut
aria-valuenow
. - Wewnętrzna treść tekstowa
<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ęp
Po zaktualizowaniu wartości użytkownicy widzący będą widzieć zmianę postępu, ale użytkownicy czytników ekranu nie otrzymają jeszcze powiadomienia o zmianie. Skup się 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()
}
Podsumowanie
Teraz, gdy wiesz, jak to zrobiłem, jak Ty byś to zrobił? 🙂
Z pewnością wprowadziłbym kilka zmian, gdybym miał jeszcze jedną szansę. Uważam, że można ulepszyć obecny komponent i spróbować zbudować go bez ograniczeń stylu pseudoklasy elementu <progress>
. Warto to sprawdzić.
Urozmaićmy nasze podejście i poznajmy wszystkie sposoby tworzenia treści w internecie.
Utwórz demo, wyślij mi na Twitterze linki, a ja dodam je do sekcji remiksów społeczności poniżej.