Una panoramica di base su come creare una barra di caricamento accessibile e adattiva ai colori con l'elemento <progress>
.
In questo post voglio condividere i miei pensieri su come creare una barra di caricamento
adattiva e accessibile ai colori con l'elemento <progress>
. Prova la
demo e visualizza la
fonte.
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.
Questa Sfida GUI ha funzionato con l'elemento <progress>
HTML esistente per ridurre lo sforzo in termini di accessibilità. I colori e i layout superano i limiti della personalizzazione per l'elemento integrato, per modernizzare il componente e adattarlo meglio ai sistemi di progettazione.
![Schede chiare e scure di ogni browser con una panoramica dell'icona adattiva dall'alto verso il basso: Safari, Firefox, Chrome.](https://web.developers.google.cn/static/articles/building/a-loading-bar-component/image/light-dark-tabs-each-br-cd04389a437d1.png?authuser=5&hl=it)
Markup
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 inoltrare tali informazioni a un utente.
<progress></progress>
Se non è presente value
, l'avanzamento dell'elemento è
indeterminato.
Per impostazione predefinita, l'attributo max
è 1, quindi l'avanzamento è compreso tra 0 e 1. Se imposti max
su 100, ad esempio, l'intervallo verrà impostato su 0-100. Ho scelto di rimanere entro i limiti 0 e 1,
traducendo i valori dei progressi 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 gli screen reader.
Per farlo, il testo dell'etichetta è riavvolto in un <span>
e vengono applicati alcuni stili
in modo che sia effettivamente fuori schermo:
<label>
<span class="sr-only">Loading progress</span>
<progress></progress>
</label>
Con il seguente CSS associato 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 agli elementi e alle aree di pagina correlati, ma per gli utenti con disabilità visiva il concetto 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, passa da aria-busy
a true
all'inizio dell'attività e a false
al termine dell'attività.
Aggiunte attributi Aria
Anche se il ruolo implicito di un elemento <progress>
è
progressbar
, l'ho reso esplicito per
i browser che non lo hanno. Ho anche aggiunto l'attributo indeterminate
per impostare esplicitamente l'elemento in uno stato sconosciuto, in modo che sia più chiaro rispetto all'assenza di value
impostato per l'elemento.
<label>
Loading
<progress
indeterminate
role="progressbar"
aria-describedby="loading-zone"
tabindex="-1"
>unknown</progress>
</label>
Utilizza tabindex="-1"
per rendere attivabile l'elemento di avanzamento 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 delle etichette dell'elemento di avanzamento. Viene aggiunto uno stato di completamento speciale che può rappresentare un segnale visivo aggiuntivo utile, ma non obbligatorio.
Layout <progress>
La larghezza dell'elemento di avanzamento non viene modificata, quindi può restringersi e aumentare
con lo 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 1e3px
di _radius
utilizza la notazione scientifica
dei numeri per esprimere un
numero elevato, in modo che border-radius
venga sempre arrotondato. Equivale a
1000px
. Mi piace usare questo dato perché il mio obiettivo è utilizzare un valore abbastanza grande da poter
impostare e dimenticare (ed è più breve da scrivere rispetto a 1000px
). Se necessario, è anche
facile renderlo ancora più grande: basta cambiare il 3 in 4, quindi 1e4px
è
equivalente a 10000px
.
overflow: hidden
è utilizzato ed è stato uno stile conflittuale. Ha semplificato alcune
cose, come non dover trasferire i valori border-radius
all'elemento
traccia e monitorare gli elementi di riempimento, ma ciò significava anche che nessun elemento secondario del progresso
poteva vivere al di fuori dell'elemento. Un'altra iterazione di questo elemento di avanzamento personalizzato potrebbe essere eseguita senza overflow: hidden
e potrebbe creare alcune opportunità per animazioni o stati di completamento migliori.
Operazione completata
I selettori CSS fanno il resto, confrontando il valore massimo con il valore. Se corrispondono, l'avanzamento è completo. Al termine, viene generato uno pseudo-elemento che viene aggiunto alla fine dell'elemento avanzamento, offrendo un ulteriore segnale visivo per il 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 utilizza i propri colori per l'elemento Progressi e si adatta al lucido chiaro e allo scuro con una sola proprietà CSS. Puoi basarti su alcuni selettori speciali per i browser.
Stili del browser chiaro e scuro
Per attivare per il tuo sito un elemento <progress>
adattivo chiaro e scuro,
color-scheme
è tutto ciò che serve.
progress {
color-scheme: light dark;
}
Colore colore avanzamento singola proprietà
Per colorare un elemento <progress>
, utilizza accent-color
.
progress {
accent-color: rebeccapurple;
}
Nota che il colore di sfondo della traccia passa da chiaro a scuro in base al
accent-color
. Il browser sta garantendo il giusto contrasto: abbastanza chiaro.
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. Nella query multimediale prefers-color-scheme
, fornisci nuovi valori di colore per la traccia e monitora l'avanzamento.
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%);
}
}
Imposta lo stato attivo sugli stili
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 lo stato attivo non mostreranno l'anello di messa a fuoco, mentre i clic della tastiera verranno visualizzati. Il video di YouTube approfondisce l'argomento e vale la pena di esaminarlo.
progress:focus-visible {
outline-color: var(--_progress);
outline-offset: 5px;
}
Stili personalizzati nei vari 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 l'intestazione Elementi, trova e attiva la casella di controllo Mostra DOM shadow dello user agent.
Stili di Safari e Chromium
I browser basati su WebKit, come Safari e Chromium, espongono ::-webkit-progress-bar
e ::-webkit-progress-value
, che consentono l'utilizzo di un sottoinsieme di CSS. Per ora, imposta background-color
utilizzando le proprietà personalizzate
create in precedenza, che si adattano al chiaro e al 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 solo lo pseudoselettore ::-moz-progress-bar
sull'elemento <progress>
. Ciò significa anche che non possiamo applicare direttamente la tinta alla traccia.
/* Firefox */
progress[value]::-moz-progress-bar {
background-color: var(--_progress);
}
Nota che per Firefox è stato impostato un colore delle tracce accent-color
, mentre per iOS Safari
è impostato un colore per le tracce azzurro. Lo stesso avviene nella 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 pseudo selettori integrati nel browser, spesso ha 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 su ::-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 uno pseudo-elemento per Chromium e viene applicato un gradiente che viene animato in avanti e indietro per tutti e tre i browser.
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 è riportata una descrizione di linear-gradient
piuttosto complessa, 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 consentono inoltre di mantenere il codice DRY poiché, ancora una volta, non possiamo raggruppare 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. È necessario un solo fotogramma chiave, quello centrale in 50%
, per creare un'animazione che ritorni al punto di partenza, ripetutamente.
@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>
o consentono di animare la barra di avanzamento. Sempre più browser supportano
l'animazione della traccia rispetto a uno pseudo-elemento, quindi eseguo l'upgrade dagli pseudo-elementi come
base a barre animate.
Pseudo-elemento Chromium
Chromium consente lo pseudo-elemento ::after
utilizzato con una posizione per coprire
l'elemento. Vengono utilizzate le proprietà personalizzate indeterminate e l'animazione indietro
e avanti 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
In Safari, le proprietà personalizzate e un'animazione vengono applicate alla barra di avanzamento degli 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
In Firefox, le proprietà personalizzate e un'animazione vengono applicate anche alla barra di avanzamento degli 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; aggiornano state.val
e quindi 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 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 l'avanzamento sia completo o meno, l'elemento <main>
correlato deve essere aggiornato per l'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 viene configurato, null
in questo utilizzo, rimuovi gli attributi value
e aria-valuenow
. In questo modo, l'elemento <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 predefinito massimo di 1 per l'avanzamento, le funzioni di incremento e decremento della demo utilizzano la matematica decimale. JavaScript e altri linguaggi non sono sempre ottimi 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 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:
- Attributo
value
dell'elemento<progress>
. - Attributo
aria-valuenow
. - I contenuti 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
}
Concentrarsi sul progresso
Con i valori aggiornati, gli utenti vedenti vedranno il cambiamento nell'avanzamento, ma agli utenti di screen reader non è ancora stato comunicato il cambiamento. Imposta lo stato attivo
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 ci ho fatto, come faresti‽ 🙂
Di sicuro vorrei apportare alcune modifiche se ci venisse offerta un'altra possibilità. 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 esplorare!
Diversificaamo 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.