Un cercle de progression (ou barre de progression circulaire) fournit un retour visuel sur l'état d'une tâche. Contrairement à une barre de progression linéaire, sa forme circulaire est idéale pour les tableaux de bord, les composants de fiche ou tout autre endroit où l'espace est limité.
Ce guide implémente un cercle de progression en :
- Utiliser l'élément
<progress>natif comme base sémantique pour s'assurer que le composant est accessible aux lecteurs d'écran et aux utilisateurs de clavier dès le départ. - Styliser le composant avec
conic-gradient()etmask-image, ce qui permet d'obtenir un anneau entièrement responsive et personnalisable sans la complexité de la manipulation des chemins SVG. - Utilisation des propriétés CSS personnalisées et de
@propertypour permettre des transitions fluides et accélérées par GPU du remplissage de la progression.
Cette approche est préférable aux solutions basées uniquement sur SVG, car elle utilise l'élément sémantique <progress> plutôt qu'ARIA, et s'intègre plus facilement aux systèmes de mise en page, aux systèmes de conception et à la typographie existants.
Consultez la section Spinner de chargement pour gérer les états de chargement indéterminés.
Implémentation
1. Annoter
Utilisez un wrapper pour contenir à la fois l'anneau visuel et le contenu central facultatif. L'élément <progress> reste la source de vérité sémantique. Utilisez une classe utilitaire pour masquer visuellement la barre de progression native tout en la gardant accessible.
<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. Styles
Masquer l'UI native
Pour appliquer un style d'anneau de progression à l'élément <progress>, masquez d'abord le style de barre de progression par défaut du navigateur.
/* 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;
}
Conteneur et anneau
Le wrapper fournit le contexte de positionnement. L'élément <progress> gère le dégradé visuel.
.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;
}
Vous pouvez également utiliser un radial-gradient pour créer des extrémités arrondies.
Activer les transitions fluides avec @property
Pour animer le cercle de progrès de manière fluide lorsque la valeur change, enregistrez --value en tant que propriété personnalisée numérique.
Les utilisateurs sensibles aux mouvements peuvent trouver la transition entre les valeurs déroutante. Respectez la requête média prefers-reduced-motion en définissant une durée de 0 seconde (immédiate) par défaut et une durée plus longue pour les utilisateurs sans préférence.
@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. Mises à jour de la progression
Mettez à jour l'attribut value de l'élément <progress> et le contenu textuel de .progress-ring-content (si vous affichez un pourcentage) chaque fois que la valeur change.
4. État de réussite facultatif
Vous pouvez utiliser le sélecteur d'attribut CSS pour mettre à jour automatiquement l'apparence de l'anneau (par exemple, en changeant la couleur en vert) lorsque la tâche est terminée.
/* Change the fill color to green when the progress reaches 100% */
progress.progress-ring[value="100"] {
--fill-color: #10b981;
}
Stratégies de remplacement
N'ajoutez pas de valeur de remplacement dans l'élément <progress>. Il n'est pas utilisé par les technologies d'assistance et est ignoré par tous les navigateurs récents.
Animation de remplacement
Si @property n'est pas pris en charge, l'anneau passe instantanément à la nouvelle valeur au lieu d'effectuer une transition fluide. Dans la plupart des cas, cela ne pose pas de problème et n'affecte aucune fonctionnalité.
Si la transition est absolument nécessaire, vous pouvez vérifier la compatibilité avec @property et utiliser une boucle requestAnimationFrame() pour interpoler --value dans les anciens navigateurs.
Pour les navigateurs qui ne sont pas encore compatibles avec background-clip: border-area, utilisez un mask-image pour évider le centre de l'élément <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;
}
Pour les navigateurs qui ne sont pas compatibles avec la fonction CSS attr() pour une propriété, utilisez un MutationObserver pour synchroniser automatiquement l'attribut value avec la propriété personnalisée --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"],
});
}