Una panoramica di base su come creare una barra di caricamento adattiva e accessibile ai colori con l'elemento <progress>
.
In questo post voglio condividere il mio pensiero 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 è utile in scenari come: avanzamento di un modulo,
visualizzazione di informazioni sul download o sul caricamento o anche per mostrare che
la quantità di avanzamento è sconosciuta, ma il lavoro è ancora attivo.
Questa GUI Challenge ha funzionato con
l'elemento HTML <progress>
esistente per risparmiare un po' di lavoro in termini di accessibilità. I colori e i layout spingono i limiti della personalizzazione per l'elemento integrato, per modernizzare il componente e farlo adattare meglio ai sistemi di progettazione.

Segni e linee
Ho scelto di racchiudere l'elemento <progress>
in un
<label>
in modo da
poter saltare gli attributi di relazione espliciti a favore di una
relazione implicita.
Ho anche etichettato un elemento principale interessato dallo stato di caricamento, in modo che le tecnologie di lettura dello schermo possano trasmettere queste informazioni a un utente.
<progress></progress>
Se non è presente value
, lo stato di avanzamento dell'elemento è
indeterminato.
L'attributo max
ha come valore predefinito 1, quindi l'avanzamento è compreso tra 0 e 1. Se imposti max
su 100, ad esempio, l'intervallo sarà 0-100. Ho scelto di rimanere entro i limiti 0 e 1, traducendo i valori di avanzamento in 0,5 o 50%.
Avanzamento con etichetta
In una relazione implicita, un elemento di avanzamento è racchiuso da un'etichetta come questa:
<label>Loading progress<progress></progress></label>
Nella mia demo ho scelto di includere l'etichetta solo per gli screen reader.
A questo scopo, il testo dell'etichetta viene racchiuso in un <span>
e vengono applicati alcuni stili
in modo che sia effettivamente fuori dallo schermo:
<label>
<span class="sr-only">Loading progress</span>
<progress></progress>
</label>
Con il seguente CSS di accompagnamento 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 dal caricamento in corso
Se hai una vista normale, può essere facile associare un indicatore di avanzamento
a elementi e aree della pagina correlati, ma per gli utenti con disabilità visive non è
così chiaro. Migliora questo aspetto assegnando l'attributo
aria-busy
all'elemento più in alto che cambierà al termine del caricamento.
Inoltre, indica una relazione 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, imposta aria-busy
su true
all'inizio dell'attività e su
false
al termine.
Aggiunte di attributi ARIA
Anche se il ruolo implicito di un elemento <progress>
è
progressbar
, l'ho reso esplicito
per i browser che non hanno questo ruolo implicito. Ho anche aggiunto l'attributo
indeterminate
per impostare esplicitamente l'elemento in uno stato sconosciuto, 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 di lettura dello schermo, poiché, dando il focus di avanzamento man mano che l'avanzamento cambia,
l'utente viene informato di 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 sono pensati per 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 suggerimento visivo aggiuntivo utile, ma non obbligatorio.
<progress>
Layout
La larghezza dell'elemento di avanzamento rimane 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é ognuno ha i propri stili per l'elemento.
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 per esprimere un
numero elevato, quindi border-radius
viene sempre arrotondato. È equivalente a
1000px
. Mi piace usare questo valore perché il mio obiettivo è utilizzarne uno abbastanza grande da poterlo impostare e dimenticare (ed è più breve da scrivere di 1000px
). Inoltre, è facile renderlo ancora più grande se necessario: basta cambiare il 3 in 4, quindi 1e4px
equivale a 10000px
.
overflow: hidden
è utilizzato ed è stato uno stile controverso. Ha semplificato alcune
cose, ad esempio non è necessario passare i valori border-radius
alla
traccia e agli elementi di riempimento della traccia, ma ha anche impedito che gli elementi secondari della barra di avanzamento
potessero trovarsi 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 è completato. Al termine, viene generato uno pseudo-elemento e aggiunto alla fine dell'elemento di avanzamento, fornendo un ulteriore segnale 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 porta i propri colori per l'elemento di avanzamento ed è adattabile alla modalità Chiaro e Buio con una sola proprietà CSS. Questi possono essere integrati con alcuni selettori speciali specifici del browser.
Stili del browser chiaro e scuro
Per attivare un elemento <progress>
adattivo chiaro e scuro per il tuo sito,
color-scheme
è tutto ciò che serve.
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 come il colore di sfondo della traccia cambia da chiaro a scuro a seconda del
accent-color
. Il browser garantisce un contrasto adeguato: niente male.
Colori chiari e scuri completamente personalizzati
Imposta due proprietà personalizzate sull'elemento <progress>
, una per il colore della traccia
e l'altra per il colore di avanzamento della traccia. All'interno della
query multimediale
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 messo a fuoco
in modo programmatico. Usa
:focus-visible
per
personalizzare la messa a fuoco e attivare lo stile dell'anello di messa a fuoco più intelligente. In questo modo, un clic
del mouse e lo stato attivo non mostreranno l'anello di evidenziazione, ma i clic della tastiera sì. Il
video di YouTube approfondisce l'argomento e
vale la pena di guardarlo.
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>
che ogni
browser espone. L'utilizzo dell'elemento di avanzamento è un singolo tag, ma è composto da
alcuni elementi secondari esposti tramite pseudo selettori CSS. Chrome DevTools
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 delle impostazioni a forma di ingranaggio nell'angolo in alto a destra della finestra DevTools.
- Nella sezione Elementi, individua e seleziona 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 ora, imposta background-color
utilizzando le proprietà personalizzate
create in precedenza, che si adattano alla modalità Chiaro e Buio.
/* 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);
}
Nota che Firefox ha un colore della traccia impostato da accent-color
, mentre Safari su iOS
ha una traccia azzurra. Lo stesso vale per la 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 lavora con gli pseudo-selettori integrati nel browser, spesso si utilizza un insieme limitato di proprietà CSS consentite.
Animazione del riempimento della traccia
L'aggiunta di una transizione al
inline-size
dell'elemento
di avanzamento funziona per Chromium, ma non per Safari. Firefox inoltre non utilizza una proprietà di transizione sul suo ::-moz-progress-bar
.
/* Chromium Only 😢 */
progress[value]::-webkit-progress-value {
background-color: var(--_progress);
transition: inline-size .25s ease-out;
}
Animare lo stato :indeterminate
Qui divento un po' più creativo, così posso fornire un'animazione. Viene creato uno pseudo-elemento per Chromium e viene applicato un gradiente che viene animato avanti e indietro per tutti e tre i browser.
Le proprietà personalizzate
Le proprietà personalizzate sono utili per molte cose, ma una delle mie preferite è semplicemente
dare un nome a un valore CSS altrimenti magico. Di seguito è riportato un esempio
piuttosto
linear-gradient
,
ma con un nome carino. Il suo scopo e i suoi casi d'uso possono 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 aiuteranno anche a mantenere il codice DRY, poiché ancora una volta non possiamo raggruppare questi selettori specifici del browser.
Fotogrammi chiave
L'obiettivo è un'animazione infinita che va avanti e indietro. I fotogrammi chiave
iniziali e finali verranno impostati in CSS. Per creare un'animazione che torna al punto di partenza, ancora e ancora, è necessario un solo fotogramma chiave, quello centrale a 50%
.
@keyframes progress-loading {
50% {
background-position: left;
}
}
Targeting di ogni browser
Non tutti i browser consentono la creazione di pseudo-elementi sull'elemento <progress>
stesso 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 e passo alle barre animate.
Pseudo-elemento Chromium
Chromium consente l'utilizzo dello pseudo-elemento: ::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 pseudo-barra di avanzamento dell'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 dello pseudo-elemento:
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; questi aggiornano state.val
e poi chiamano una funzione per aggiornare il
DOM.
document.querySelector('#complete').addEventListener('click', e => {
state.val = 1
setProgress()
})
setProgress()
Questa funzione è il punto in cui avviene l'orchestrazione dell'UI/UX. Per iniziare, crea una funzione setProgress()
. 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 i progressi siano completi o meno, l'elemento <main>
correlato deve essere aggiornato all'attributo
aria-busy
:
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
}
Cancella gli attributi se l'importo del caricamento è sconosciuto
Se il valore è sconosciuto o non impostato, null
in questo utilizzo, rimuovi gli attributi value
e
aria-valuenow
. In questo modo, <progress>
verrà impostato su 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 di 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 adatti
a questo scopo.
Ecco una funzione roundDecimals()
che taglierà l'eccesso dal risultato
del calcolo:
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 + "%"
}
Imposta 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 di 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
}
Focus sull'avanzamento
Con i valori aggiornati, gli utenti vedranno il cambiamento dello stato di avanzamento, ma gli utenti di screen reader non riceveranno ancora l'annuncio della modifica. Concentrati sull'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 tu?‽ 🙂
Se avessi un'altra possibilità, vorrei sicuramente apportare alcune modifiche. Penso che ci sia spazio per ripulire il componente attuale e per provare a crearne uno senza le limitazioni di stile della pseudo-classe dell'elemento <progress>
. Vale la pena esplorare.
Diversifichiamo i nostri approcci e impariamo tutti i modi per creare sul web.
Crea una demo, inviami un tweet con i link e la aggiungerò alla sezione dei remix della community qui sotto.