Un anello di progresso (o barra di avanzamento circolare) fornisce un feedback visivo sullo stato di un'attività. A differenza di una barra di avanzamento lineare, la sua forma circolare è ideale per dashboard, componenti di schede o qualsiasi altro punto in cui lo spazio è limitato.
Questa guida implementa un anello di progresso:
- Utilizzo dell'elemento
<progress>nativo come base semantica per garantire che il componente sia accessibile agli screen reader e agli utenti di tastiera fin da subito. - Stile del componente con
conic-gradient()emask-image, che consente di ottenere un anello completamente adattabile e personalizzabile senza la complessità della manipolazione del percorso SVG. - Sfruttando le proprietà personalizzate CSS e
@propertyper consentire transizioni fluide e con accelerazione GPU del riempimento della barra di avanzamento.
Questo approccio è preferibile alle soluzioni solo SVG perché utilizza l'elemento semantico <progress> anziché ARIA e si integra più facilmente con il layout, i sistemi di progettazione e la tipografia esistenti.
Consulta la sezione Rotellina di caricamento per gestire gli stati di caricamento indeterminati.
Implementazione
1. Segni e linee
Utilizza un wrapper per contenere sia l'anello visivo sia i contenuti centrali facoltativi. L'elemento <progress> rimane la fonte di verità semantica. Utilizza una classe di utilità per nascondere visivamente la barra di avanzamento nativa, mantenendola accessibile.
<div class="ring-wrapper">
<progress value="75" max="100" aria-label="Task progress" class="progress-ring"></progress>
<!-- Optional: Content to display in the center -->
<div class="ring-content">
75%
</div>
</div>
2. Stili
Nascondere la UI nativa
Per applicare lo stile all'elemento <progress> come anello di progresso, nascondi innanzitutto lo stile predefinito del browser per le barre di avanzamento.
/* Hide native bars */
progress.loading-spinner:indeterminate::-webkit-progress-bar {
display: none;
background: none;
}
progress.loading-spinner:indeterminate::-webkit-progress-value {
display: none;
background: none;
}
progress.loading-spinner:indeterminate::-moz-progress-bar {
display: none;
background: none;
}
progress.loading-spinner:indeterminate::slider-fill {
display: none;
background: none;
}
Contenitore e anello
Il wrapper fornisce il contesto di posizionamento. L'elemento <progress> gestisce il gradiente visivo.
.progress-ring-wrapper {
position: relative;
display: grid;
place-items: center;
}
progress.progress-ring {
--size: 150px;
--thickness: 16px;
--track-color: #f1f5f9;
--fill-color: #3b82f6;
--value: attr(value type(<number>));
width: var(--size);
height: var(--size);
transition: --value 0.4s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: 50%;
background: conic-gradient(
var(--fill-color) calc(var(--value) * 1%),
var(--track-color) 0
);
/* MANDATORY: Clip the background to the border-area. */
background-clip: border-area;
border: var(--thickness) solid transparent;
background-origin: border-box;
}
.progress-ring-content {
/* Positioned in the center of the wrapper */
position: absolute;
}
Puoi anche utilizzare un radial-gradient per creare estremità arrotondate.
Abilita transizioni fluide con @property
Per animare l'anello di progresso in modo fluido quando il valore cambia, registra --value come proprietà personalizzata numerica.
Gli utenti con sensibilità al movimento potrebbero trovare disorientante la transizione tra i valori. Rispetta la query supporti prefers-reduced-motion impostando una durata predefinita di 0 secondi (immediata) e un tempo più lungo per gli utenti che non hanno preferenze.
@property --value {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
progress.progress-ring {
transition: --value 0s ease-in-out;
@media (prefers-reduced-motion: no-preference) {
transition-duration: 0.4s;
}
}
3. Aggiornamenti sull'avanzamento
Aggiorna l'attributo value nell'elemento <progress> e i contenuti testuali di .progress-ring-content (se viene visualizzata una percentuale) ogni volta che il valore cambia.
4. Stato di esito positivo facoltativo
Puoi utilizzare il selettore di attributi CSS per aggiornare automaticamente l'aspetto dell'anello (ad esempio, cambiando il colore in verde) quando l'attività viene completata.
/* Change the fill color to green when the progress reaches 100% */
progress.progress-ring[value="100"] {
--fill-color: #10b981;
}
Strategie di riserva
Non aggiungere un valore di riserva all'interno dell'elemento <progress>. Non viene utilizzato dalle tecnologie per la disabilità e viene ignorato da tutti i browser moderni.
Animazione di riserva
Se @property non è supportato, l'anello passerà istantaneamente al nuovo valore anziché eseguire una transizione graduale. Nella maggior parte dei casi, questo non è un problema e non interrompe alcuna funzionalità.
Se la transizione è assolutamente necessaria, puoi verificare il supporto di @property e utilizzare un ciclo requestAnimationFrame() per interpolare --value nei browser meno recenti.
Per i browser che non supportano ancora background-clip: border-area, utilizza un mask-image per svuotare il centro dell'elemento <progress>.
@supports not (background-clip: border-area) {
mask-image: radial-gradient(
transparent calc(50% - var(--thickness)),
black calc(50% - var(--thickness) + 0.5px)
);
border: 0;
}
Per i browser che non supportano la funzione CSS attr() per nessuna proprietà, utilizza un MutationObserver per sincronizzare automaticamente l'attributo value con la proprietà personalizzata --value.
if (!CSS.supports("width: attr(value type(<number>))")) {
const observer = new MutationObserver(() => {
progress.style.setProperty("--value", progress.getAttribute("value"));
});
observer.observe(progress, {
attributes: true,
attributeFilter: ["value"],
});
}