Una panoramica di base su come creare una barra di caricamento adattabile al colore e accessibile con l'elemento <progress>
.
In questo post voglio condividere alcune idee su come creare una barra di caricamento adattabile al colore e accessibile con l'elemento <progress>
. Prova la demo e visualizza il codice sorgente.
Se preferisci i video, ecco una versione di questo post su YouTube:
Panoramica
L'elemento
<progress>
fornisce agli utenti un feedback visivo e udibile sul completamento. Questo feedback visivo è prezioso per scenari come l'avanzamento in un modulo, la visualizzazione di informazioni in merito a download o caricamento o persino che l'importo dell'avanzamento non è noto, ma il lavoro è ancora attivo.
Questo sfide della GUI ha utilizzato
l'elemento HTML <progress>
esistente per risparmiare un po' di impegno in termini di accessibilità. I colori e i layout spingono i limiti della personalizzazione dell'elemento integrato per modernizzare il componente e adattarlo meglio ai sistemi di progettazione.
Segni e linee
Ho scelto di aggregare l'elemento <progress>
in un
<label>
in modo da
ignorare gli attributi di relazione esplicita a favore di una
relazione implicita.
Ho anche etichettato un elemento principale interessato dallo stato di caricamento, in modo che le tecnologie di screen reader possano ritrasmettere queste informazioni a un utente.
<progress></progress>
Se non è presente value
, l'avanzamento dell'elemento è
indeterminato.
Il valore predefinito dell'attributo max
è 1, quindi l'avanzamento è compreso tra 0 e 1. Impostando max
su 100, ad esempio, l'intervallo viene impostato su 0-100. Ho scelto di rispettare i limiti di 0
e 1, traducendo i valori di avanzamento in 0,5 o 50%.
Avanzamento del wrapping delle etichette
In una relazione implicita, un elemento di avanzamento è aggregato da un'etichetta come la seguente:
<label>Loading progress<progress></progress></label>
Nella mia demo ho scelto di includere l'etichetta solo per screen reader.
Per farlo, inserisci il testo dell'etichetta in un <span>
e applica alcuni stili in modo che non sia visibile sullo schermo:
<label>
<span class="sr-only">Loading progress</span>
<progress></progress>
</label>
Con il seguente CSS aggiuntivo di 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;
}
Area interessata dall'avanzamento del caricamento
Se hai una vista sana, può essere facile associare un indicatore di avanzamento
con elementi correlati e aree della pagina, ma per gli utenti con disabilità visive non è così chiaro. Migliora la situazione assegnando l'attributo
aria-busy
all'elemento più in alto, che cambierà al termine del caricamento.
Inoltre, indica un rapporto tra l'avanzamento e la zona di caricamento
con
aria-describedby
.
<main id="loading-zone" aria-busy="true">
…
<progress aria-describedby="loading-zone"></progress>
</main>
Da JavaScript, passa da aria-busy
a true
all'inizio dell'attività e a false
al termine dell'attività.
Aggiunte attributi Aria
Sebbene il ruolo implicito di un elemento <progress>
sia
progressbar
, l'ho reso esplicito
per i browser che non dispongono di questo ruolo implicito. Ho anche aggiunto l'attributo
indeterminate
per impostare esplicitamente lo stato dell'elemento su Sconosciuto, il che è
più chiaro rispetto all'osservazione che l'elemento non ha value
impostato.
<label>
Loading
<progress
indeterminate
role="progressbar"
aria-describedby="loading-zone"
tabindex="-1"
>unknown</progress>
</label>
Utilizza
tabindex="-1"
per rendere l'elemento di avanzamento selezionabile da JavaScript. Questo è importante per
la tecnologia degli screen reader, poiché impostare lo stato attivo sull'avanzamento quando cambia
l'avanzamento comunicherà all'utente quanto è stato raggiunto l'avanzamento aggiornato.
Stili
L'elemento di avanzamento è un po' complicato per quanto riguarda lo stile. Gli elementi HTML integrati hanno parti nascoste speciali che possono essere difficili da selezionare e spesso offrono solo un insieme limitato di proprietà da impostare.
Layout
Gli stili di layout hanno lo scopo di consentire una certa flessibilità nelle dimensioni e nella posizione dell'etichetta dell'elemento di avanzamento. Viene aggiunto uno stato di completamento speciale che può essere un indicatore visivo aggiuntivo utile, ma non obbligatorio.
<progress>
Layout
La larghezza dell'elemento di avanzamento viene lasciata invariata in modo che possa ridursi e aumentare in base allo spazio necessario nel design. Gli stili integrati vengono rimossi impostando appearance
e border
su none
. In questo modo l'elemento può essere
normalizzato nei vari browser, poiché ogni browser ha i propri stili.
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;
}
Il valore di 1e3px
per _radius
utilizza la notazione scientifica
del numero per esprimere un
numero elevato, pertanto border-radius
viene sempre arrotondato. È equivalente a
1000px
. Mi piace utilizzarlo perché il mio obiettivo è usare un valore sufficientemente elevato da poterlo impostare e dimenticarlo (ed è più breve da scrivere di 1000px
). È anche facile aumentarlo ulteriormente, se necessario: basta sostituire il 3 con un 4, quindi 1e4px
è equivalente a 10000px
.
overflow: hidden
è uno stile controverso e viene utilizzato. Ha semplificato alcune
cose, ad esempio non è necessario trasferire i valori border-radius
all'elemento
traccia e monitorare gli elementi di riempimento, ma ciò significava anche che nessun elemento secondario dell'avanzamento
poteva vivere al di fuori dell'elemento. Un'altra iterazione di questo elemento di avanzamento personalizzato potrebbe essere eseguita senza overflow: hidden
e potrebbe aprire alcune opportunità per animazioni o stati di completamento migliori.
Operazione completata
I selettori CSS fanno il lavoro più difficile confrontando il valore massimo con il valore e, se corrispondono, l'avanzamento è completo. Al termine, viene generato uno pseudo-elemento e aggiunto alla fine dell'elemento di avanzamento, fornendo un ulteriore indicatore visivo del completamento.
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);
}
Colore
Il browser offre i propri colori per l'elemento di avanzamento ed è adatto a modalità chiare e scure con una sola proprietà CSS. Questa funzionalità può essere ampliata con alcuni selezionatori speciali specifici del browser.
Stili del browser chiaro e scuro
Per attivare un elemento <progress>
adattabile scuro e chiaro sul tuo sito, è sufficiente color-scheme
.
progress {
color-scheme: light dark;
}
Colore di riempimento dell'avanzamento di una singola proprietà
Per colorare un elemento <progress>
, utilizza accent-color
.
progress {
accent-color: rebeccapurple;
}
Nota che il colore di sfondo della traccia cambia da chiaro a scuro a seconda del valore di accent-color
. Il browser garantisce un contrasto adeguato: molto pratico.
Colori chiari e scuri completamente personalizzati
Imposta due proprietà personalizzate sull'elemento <progress>
, una per il colore del percorso e l'altra per il colore di avanzamento del tracciamento. All'interno della query media
prefers-color-scheme
, fornisci nuovi valori di colore per la traccia e l'avanzamento della traccia.
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%);
}
}
Stili di messa a fuoco
In precedenza abbiamo assegnato all'elemento un indice di tabulazione negativo in modo che potesse essere incentrato
sulla programmazione. Usa :focus-visible
per personalizzare la messa a fuoco in modo da attivare lo stile più intelligente dell'anello di messa a fuoco. In questo modo, un clic del mouse e l'evidenziazione dell'elemento attivo non mostreranno l'anello di evidenza, ma i clic della tastiera sì. Il
video di YouTube approfondisce questo argomento e merita di essere guardato.
progress:focus-visible {
outline-color: var(--_progress);
outline-offset: 5px;
}
Stili personalizzati su più browser
Personalizza gli stili selezionando le parti di un elemento <progress>
esposte da ciascun browser. L'utilizzo dell'elemento progress è un singolo tag, ma è formato da pochi elementi secondari esposti tramite pseudoselettori CSS. Chrome DevTools ti mostrerà questi elementi se attivi l'impostazione:
- Fai clic con il tasto destro del mouse sulla pagina e seleziona Ispeziona elemento per visualizzare DevTools.
- Fai clic sull'icona a forma di ingranaggio delle impostazioni nell'angolo in alto a destra della finestra DevTools.
- Sotto la voce Elementi, trova e abilita la casella di controllo Mostra DOM shadow dell'agente utente.
Stili Safari e Chromium
I browser basati su WebKit, come Safari e Chromium, espongono ::-webkit-progress-bar
e ::-webkit-progress-value
, che consentono di utilizzare un sottoinsieme di CSS. Per il momento, imposta background-color
utilizzando le proprietà personalizzate
create in precedenza, che si adattano a modalità chiara e scura.
/* Safari/Chromium */
progress[value]::-webkit-progress-bar {
background-color: var(--_track);
}
progress[value]::-webkit-progress-value {
background-color: var(--_progress);
}
Stili di Firefox
Firefox espone lo pseudo selettore ::-moz-progress-bar
solo sull'elemento
<progress>
. Ciò significa anche che non possiamo colorare direttamente la traccia.
/* Firefox */
progress[value]::-moz-progress-bar {
background-color: var(--_progress);
}
Tieni presente che Firefox ha un colore traccia impostato su accent-color
, mentre Safari per iOS ha una traccia azzurra. È lo stesso in modalità Buio: Firefox ha una traccia scura, ma non il colore personalizzato che abbiamo impostato e funziona nei browser basati su Webkit.
Animazione
Quando si utilizzano gli pseudo-selettori integrati del browser, spesso si ha a che fare con un insieme limitato di proprietà CSS consentite.
Animazione del riempimento della traccia
L'aggiunta di una transizione all'elemento
inline-size
dell'elemento di avanzamento funziona per Chromium, ma non per Safari. Inoltre, Firefox non utilizza una proprietà di transizione per il suo ::-moz-progress-bar
.
/* Chromium Only 😢 */
progress[value]::-webkit-progress-value {
background-color: var(--_progress);
transition: inline-size .25s ease-out;
}
Animazione dello stato :indeterminate
Qui posso ottenere un po' più di creatività in modo da poter fornire un'animazione. Viene creato un pseudo-elemento per Chromium e viene applicato un gradiente animato avanti e indietro per tutti e tre i browser.
Le proprietà personalizzate
Le proprietà personalizzate sono ottime per molti aspetti, ma una delle mie preferite è semplicemente
assegnare un nome a un valore CSS altrimenti magico. Di seguito è riportato un linear-gradient
abbastanza complesso, ma con un bel nome. Lo scopo e i casi d'uso devono essere compresi chiaramente.
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;
}
Le proprietà personalizzate contribuiranno inoltre a mantenere il codice DRY, poiché ancora una volta non possiamo agrupare questi selettori specifici del browser.
I fotogrammi chiave
L'obiettivo è un'animazione infinita che va avanti e indietro. I fotogrammi chiave di inizio e fine
saranno impostati in CSS. Per creare un'animazione che ritorna sempre al punto di partenza, è necessario un solo fotogramma chiave, quello intermedio in 50%
.
@keyframes progress-loading {
50% {
background-position: left;
}
}
Scegliere come target ogni browser
Non tutti i browser consentono la creazione di pseudo-elementi nell'elemento <progress>
stessa o l'animazione della barra di avanzamento. Più browser supportano
l'animazione della traccia rispetto a uno pseudo-elemento, quindi eseguo l'upgrade dagli pseudo-elementi come
base alle barre animate.
Pseudoelemento Chromium
Chromium consente l'uso dell'elemento pseudo: ::after
con una posizione per coprire
l'elemento. Vengono utilizzate le proprietà personalizzate indeterminate e l'animazione avanti e indietro funziona molto bene.
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);
}
Barra di avanzamento di Safari
Per Safari, le proprietà personalizzate e un'animazione vengono applicate alla barra di avanzamento dell'pseudo-elemento:
progress:indeterminate::-webkit-progress-bar {
background: var(--_indeterminate-track);
background-size: var(--_indeterminate-track-size);
background-position: right;
animation: var(--_indeterminate-track-animation);
}
Barra di avanzamento di Firefox
Per Firefox, le proprietà personalizzate e un'animazione vengono applicate anche alla barra di avanzamento dell'elemento pseudo:
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 svolge un ruolo importante con l'elemento <progress>
. Controlla il valore inviato all'elemento e garantisce che nel documento siano presenti informazioni sufficienti per gli screen reader.
const state = {
val: null
}
La demo offre pulsanti per controllare l'avanzamento; aggiornano state.val
e quindi chiamano una funzione per aggiornare il
DOM.
document.querySelector('#complete').addEventListener('click', e => {
state.val = 1
setProgress()
})
setProgress()
In questa funzione avviene l'orchestrazione dell'UI/UX. Per iniziare, crea una funzionesetProgress()
. Non sono necessari parametri perché ha accesso all'oggetto state
, all'elemento di avanzamento e alla zona <main>
.
const setProgress = () => {
}
Impostazione dello stato di caricamento nella zona <main>
A seconda che l'avanzamento sia completo o meno, l'elemento <main>
correlato richiede un aggiornamento dell'attributo aria-busy
:
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
}
Cancella gli attributi se la quantità di caricamento è sconosciuta
Se il valore è sconosciuto o non impostato, null
in questo utilizzo, rimuovi gli attributi value
e
aria-valuenow
. In questo modo, il valore <progress>
diventerà indeterminato.
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
if (state.val === null) {
progress.removeAttribute('aria-valuenow')
progress.removeAttribute('value')
progress.focus()
return
}
}
Risolvere i problemi di matematica decimale in JavaScript
Poiché ho scelto di mantenere il valore massimo predefinito di 1 per l'avanzamento, le funzioni di incremento e decremento della demo utilizzano la matematica decimale. JavaScript e altri linguaggi non sono sempre molto bravi in questo.
Ecco una funzione roundDecimals()
che taglia l'eccesso dal risultato matematico:
const roundDecimals = (val, places) =>
+(Math.round(val + "e+" + places) + "e-" + places)
Arrotonda il valore in modo che possa essere presentato e sia leggibile:
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 + "%"
}
Impostare il valore per gli screen reader e lo stato del browser
Il valore viene utilizzato in tre posizioni nel DOM:
- L'attributo
value
dell'elemento<progress>
. - L'attributo
aria-valuenow
. - Il contenuto del testo interno di
<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
}
Dare l'attenzione all'avanzamento
Con i valori aggiornati, gli utenti vedenti vedranno la modifica dell'avanzamento, ma gli utenti degli screen reader non hanno ancora ricevuto l'annuncio della modifica. Metti in primo piano l'elemento<progress>
e il browser annuncerà l'aggiornamento.
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()
}
Conclusione
Ora che sai come ho fatto, come faresti? 🙂
Se mi venisse data un'altra possibilità, vorrei apportare alcune modifiche. Penso che ci sia spazio per ripulire il componente corrente e per provare a crearne uno senza le limitazioni di stile pseudo-classe dell'elemento <progress>
. Vale la pena esplorarlo.
Diversifichiamo i nostri approcci e impariamo tutti i modi per creare sul web.
Crea una demo, twittami con i link e io la aggiungerò alla sezione dei remix della community qui sotto.