Voortgangsring

Een voortgangsring (of cirkelvormige voortgangsbalk) geeft visuele feedback over de status van een taak. In tegenstelling tot een lineaire voortgangsbalk is de cirkelvorm ideaal voor dashboards, kaartcomponenten of andere plekken waar de ruimte beperkt is.

In deze handleiding wordt een voortgangsring geïmplementeerd door:

  • Het systeemeigen <progress>-element gebruiken als semantische basis om te zorgen dat de component standaard toegankelijk is voor schermlezers en toetsenbordgebruikers.
  • De component stylen met conic-gradient() en mask-image, waardoor een volledig responsieve en aanpasbare ring mogelijk is zonder de complexiteit van SVG-padmanipulatie.
  • Aangepaste CSS-eigenschappen en @property gebruiken om soepele, door de GPU versnelde overgangen van de voortgangsvulling mogelijk te maken.

Deze aanpak heeft de voorkeur boven oplossingen die alleen SVG gebruiken, omdat het semantische <progress>-element wordt gebruikt in plaats van ARIA en het makkelijker kan worden geïntegreerd met bestaande lay-outs, ontwerpsystemen en typografie.

Ga naar de laadspinner voor informatie over hoe je onbepaalde laadstatussen verwerkt.

Implementatie

1. Opmaak

Gebruik een wrapper voor zowel de visuele ring als de optionele content in het midden. Het element <progress> blijft de semantische bron van waarheid. Gebruik een hulpprogrammaklasse om de native voortgangsbalk visueel te verbergen, maar wel toegankelijk te houden.

<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. Stijlen

Systeemeigen UI verbergen

Als je het element <progress> wilt opmaken als voortgangsring, verberg je eerst de standaardbrowserstijl voor voortgangsbalken.

/* 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;
}

Container en ring

De wrapper biedt de positioneringscontext. Het element <progress> verwerkt de visuele kleurovergang.

.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;
}

Je kunt ook een radial-gradient gebruiken om afgeronde eindkappen te maken.

Zet soepele overgangen aan met @property

Als je de voortgangsring vloeiend wilt animeren wanneer de waarde verandert, registreer je --value als een numerieke aangepaste eigenschap.

Gebruikers die gevoelig zijn voor beweging, kunnen de overgang tussen waarden desoriënterend vinden. Respecteer de mediaquery prefers-reduced-motion door standaard een duur van 0 seconden (onmiddellijk) te gebruiken en een langere tijd in te stellen voor gebruikers zonder voorkeur.

@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. Voortgangsupdates

Update het kenmerk value van het element <progress> en de tekstcontent van .progress-ring-content (als er een percentage wordt getoond) als de waarde verandert.

4. Optionele successtatus

Je kunt de css-kenmerkkiezer gebruiken om de weergave van de ring automatisch te updaten (bijvoorbeeld door de kleur te wijzigen in groen) als de taak is voltooid.

/* Change the fill color to green when the progress reaches 100% */
progress.progress-ring[value="100"] {
  --fill-color: #10b981;
}

Reservestrategieën

Voeg geen reservewaarde toe aan het element <progress>. Het wordt niet gebruikt door ondersteunende technologie en wordt genegeerd door alle moderne browsers.

Animatiefallback

Als @property niet wordt ondersteund, springt de ring onmiddellijk naar de nieuwe waarde in plaats van een soepele overgang. In de meeste gevallen is dit geen probleem en werkt alles nog steeds.

Als de overgang absoluut noodzakelijk is, kun je controleren op @property-ondersteuning en een requestAnimationFrame()-lus gebruiken om --value te interpoleren in oudere browsers.

Voor browsers die background-clip: border-area nog niet ondersteunen, gebruik je een mask-image om het midden van het <progress>-element uit te hollen.

@supports not (background-clip: border-area) {
  mask-image: radial-gradient(
    transparent calc(50% - var(--thickness)),
    black calc(50% - var(--thickness) + 0.5px)
  );
  border: 0;
}

Voor browsers die de CSS-functie attr() niet ondersteunen voor een property, gebruikt u een MutationObserver om het kenmerk value automatisch te synchroniseren met de aangepaste eigenschap --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"],
  });
}