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 è utile per scenari come: avanzamento in un modulo,
visualizzazione di informazioni di download o caricamento o anche per mostrare che
l'importo dell'avanzamento è sconosciuto, 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 racchiudere l'elemento <progress>
in un
<label>
in modo da poter saltare gli attributi della relazione esplicita in 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%.
Aggiornamento con etichetta inserita nel codice
In una relazione implicita, un elemento di avanzamento è racchiuso in un'etichetta come questa:
<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>
In JavaScript, passa da aria-busy
a true
all'inizio dell'attività e a
false
al termine.
Aggiunta di attributi Aria
Anche se il ruolo implicito di un elemento <progress>
è
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é l'assegnazione dell'attenzione all'avanzamento man mano che cambia, consente di comunicare all'utente il livello di 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 uniformato su tutti i browser, poiché ogni browser 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
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 grande da poterlo impostare e dimenticare (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 è stato necessario passare i valori border-radius
al tracciato e agli elementi di riempimento del tracciato, ma ha anche significato che nessun elemento secondario del progresso poteva 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 è 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 utile.
Colori chiari e scuri completamente personalizzati
Imposta due proprietà personalizzate sull'elemento <progress>
, una per il colore del canale
e l'altra per il colore dell'avanzamento del canale. 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 selezionato tramite codice. Utilizza
:focus-visible
per
personalizzare lo stato attivo e attivare lo stile dell'anello di messa a fuoco più intelligente. In questo modo, un clic del mouse e l'evidenziazione dell'elemento attivo non mostreranno l'anello di evidenziazione, 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 di avanzamento è un singolo tag, ma è costituito da alcuni elementi secondari esposti tramite gli pseudo-selettori CSS. Se attivi l'impostazione, Chrome DevTools
mostrerà questi elementi:
- Fai clic con il tasto destro del mouse sulla pagina e seleziona Ispeziona elemento per aprire DevTools.
- Fai clic sull'icona a forma di ingranaggio Impostazioni nell'angolo in alto a destra della finestra di 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 della 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 al
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 essere un po' più creativo e fornire un'animazione. Viene creato uno 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 molte cose, ma una delle mie preferite è semplicemente dare 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 sono chiari.
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 verranno impostati in CSS. Per creare un'animazione che ritorna all'inizio, più e più volte, è necessario un solo fotogramma chiave, quello intermedio in 50%
.
@keyframes progress-loading {
50% {
background-position: left;
}
}
Targeting per ciascun 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 poi richiamano 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 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 richiede un aggiornamento dell'attributo aria-busy
:
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
}
Cancellare 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 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 molto bravi in questo.
Ecco una funzione roundDecimals()
che elimina le parti in 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 riorganizzare il componente attuale e per provare a crearne uno senza le limitazioni dello stile delle pseudoclassi dell'elemento <progress>
. Vale la pena esplorarlo.
Diversifichiamo i nostri approcci e impariamo tutti i modi per creare sul web.
Crea una demo, twittami i link e io la aggiungerò alla sezione dei remix della community di seguito.