Fortschritts-Ring

Ein Fortschrittsring (oder eine kreisförmige Fortschrittsanzeige) bietet visuelles Feedback zum Status einer Aufgabe. Im Gegensatz zu einer linearen Fortschrittsanzeige eignet sich die kreisförmige Variante ideal für Dashboards, Kartenkomponenten oder andere Bereiche mit begrenztem Platzangebot.

In diesem Leitfaden wird ein Fortschrittsring so implementiert:

  • Verwendung des nativen <progress>-Elements als semantische Grundlage, damit die Komponente für Screenreader und Tastaturnutzer sofort zugänglich ist.
  • Die Komponente wird mit conic-gradient() und mask-image formatiert, was einen vollständig responsiven und anpassbaren Ring ermöglicht, ohne dass komplexe SVG-Pfadmanipulationen erforderlich sind.
  • Benutzerdefinierte CSS-Eigenschaften und @property werden verwendet, um flüssige, GPU-beschleunigte Übergänge der Fortschrittsanzeige zu ermöglichen.

Dieser Ansatz wird gegenüber reinen SVG-Lösungen bevorzugt, da er das semantische <progress>-Element anstelle von ARIA verwendet und sich leichter in bestehende Layouts, Designsysteme und Typografie einbinden lässt.

Informationen zum Umgang mit unbestimmten Ladestatus finden Sie unter Ladeanzeige.

Implementierung

1. Markieren & Zeichnen

Verwenden Sie einen Wrapper, der sowohl den visuellen Ring als auch den optionalen Inhalt in der Mitte enthält. Das <progress>-Element bleibt die semantische Source of Truth. Verwenden Sie eine Hilfsklasse, um die native Fortschrittsanzeige visuell auszublenden, aber zugänglich zu halten.

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

Native UI ausblenden

Wenn Sie das <progress>-Element als Fortschrittsring formatieren möchten, müssen Sie zuerst das Standard-Browser-Styling für Fortschrittsanzeigen ausblenden.

/* 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 und Ring

Der Wrapper stellt den Positionierungskontext bereit. Das <progress>-Element verarbeitet den visuellen Farbverlauf.

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

Sie können auch ein radial-gradient verwenden, um abgerundete Endkappen zu erstellen.

Fließende Übergänge mit @property aktivieren

Wenn du den Fortschrittsring bei einer Wertänderung flüssig animieren möchtest, registriere --value als numerische benutzerdefinierte Eigenschaft.

Nutzer mit Bewegungsempfindlichkeit können den Übergang zwischen den Werten als verwirrend empfinden. Berücksichtigen Sie die Medienabfrage prefers-reduced-motion, indem Sie standardmäßig eine Dauer von 0 Sekunden (sofort) festlegen und für Nutzer ohne Präferenz eine längere Zeit einstellen.

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

Aktualisieren Sie das Attribut value des Elements <progress> und den Textinhalt von .progress-ring-content (wenn ein Prozentsatz angezeigt wird), wenn sich der Wert ändert.

4. Optionaler Erfolgsstatus

Mit dem CSS-Attributselektor können Sie das Aussehen des Rings automatisch aktualisieren lassen, z.B. die Farbe in Grün ändern, wenn die Aufgabe abgeschlossen ist.

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

Fallback-Strategien

Fügen Sie im <progress>-Element keinen Fallback-Wert hinzu. Es wird nicht von Hilfstechnologien verwendet und von allen modernen Browsern ignoriert.

Fallback-Animation

Wenn @property nicht unterstützt wird, springt der Ring sofort zum neuen Wert, anstatt einen flüssigen Übergang zu zeigen. In den meisten Fällen ist das in Ordnung und beeinträchtigt die Funktionalität nicht.

Wenn der Übergang unbedingt erforderlich ist, können Sie die Unterstützung für @property prüfen und eine requestAnimationFrame()-Schleife verwenden, um --value in älteren Browsern zu interpolieren.

Bei Browsern, die background-clip: border-area noch nicht unterstützen, kannst du auf ein mask-image zurückgreifen, um die Mitte des <progress>-Elements auszuhöhlen.

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

Für Browser, die die CSS-Funktion attr() für keine Eigenschaft unterstützen, verwenden Sie ein MutationObserver, um das Attribut value automatisch mit der benutzerdefinierten Eigenschaft --value zu synchronisieren.

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"],
  });
}