Rotierendes Ladesymbol

Ein Ladekreisel (oder Aktivitätsindikator) informiert Nutzer darüber, dass ein Prozess im Gange ist, wenn die genaue Dauer unbekannt ist. Im Gegensatz zu einem Fortschritts-Ring ist ein Spinner „unbestimmt“ und signalisiert die Aktivität in der Regel durch eine Animation.

In diesem Leitfaden wird ein Ladekreisel so implementiert:

  • Verwendung des nativen <progress>-Elements als semantische Grundlage. Wenn das Attribut value weggelassen wird, behandelt der Browser die Fortschrittsanzeige als unbestimmt, sodass sie von Hilfstechnologien korrekt angesagt wird.
  • Die Komponente wird mit conic-gradient() formatiert, um eine visuelle „Spur“ zu erstellen, und mit background-clip: border-area, um die Mitte zu einem Ring auszuhöhlen.
  • Der Spinner wird mithilfe von CSS-Transformationen effizient animiert und prefers-reduced-motion wird berücksichtigt, um allen Nutzern eine angenehme Nutzung zu ermöglichen.

Informationen zum Umgang mit bestimmten Aufgaben mit bekannter Dauer findest du unter Fortschritts-Ring.

Implementierung

1. Markieren & Zeichnen

Verwenden Sie das native <progress>-Element sowohl als semantische „Source of Truth“ als auch als visuelle Komponente. Ohne ein value-Attribut ist der Status implizit unbestimmt.

<progress aria-label="Loading" class="loading-spinner"></progress>

2. Stile

Native UI ausblenden

Wenn Sie das <progress>-Element als Ladekreis formatieren möchten, müssen Sie zuerst die Standardbrowserformatierung für unbestimmte 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;
}

Spinner Ring and Trail

Der Spinner verwendet ein conic-gradient, um eine visuelle Spur zu erstellen.

@property --arc-start {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}
@property --arc-end {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}
/* Use a custom property to conditionally include the dash animation */
@property --progress-dash-animation {
  syntax: "*";
  inherits: false;
  initial-value: , progress-dash 3s ease-in-out infinite;
}

progress.loading-spinner:indeterminate {
  --_from: calc(90deg + var(--arc-start, 0deg));
  --_to: calc(90deg + var(--arc-end, 158deg));
  --size: 40px;
  --thickness: 2px;
  --spinner-color: #3b82f6;
  --track-color: #e2e5e7;
  --spinner-duration: 1.5s;
  --_used-spinner-duration: var(--spinner-duration);
  --spinner-timing: linear;

  position: relative;
  width: var(--size);
  height: var(--size);
  border-radius: 50%;
  appearance: none;

  /* Create the fading trail with dynamic angles */
  background: conic-gradient(
      from var(--_from),
      var(--spinner-color) calc(var(--_to) - var(--_from)),
      transparent 0
    )
    var(--track-color);

  @supports (background-clip: border-area) {
    background-clip: border-area;
    border: var(--thickness) solid transparent;
    background-origin: border-box;
  }

  /* ... fallback for background-clip: border-area ... */

  /* The dash animation is only included if @property is supported */
  animation:
    progress-spin var(--_used-spinner-duration) linear infinite
    var(--progress-dash-animation, );
}

@keyframes progress-spin {
  to {
    rotate: 1turn;
  }
}

@keyframes progress-dash {
  from {
    --arc-start: 0deg;
    --arc-end: 3deg;
  }
  50% {
    --arc-start: 100deg;
    --arc-end: 358deg;
  }
  to {
    --arc-start: 360deg;
    --arc-end: 363deg;
  }
}

Berücksichtigung von Bewegungseinstellungen

Nutzer mit Bewegungsempfindlichkeit können sich durch schnell drehende Elemente desorientiert fühlen. Beachten Sie immer die Medienabfrage prefers-reduced-motion. Legen Sie das interne Attribut --_used-spinner-duration fest, um den --spinner-duration-Wert des Nutzers zu überschreiben.

@media (prefers-reduced-motion: reduce) {
  .loading-spinner {
    /* Slow down the animation significantly rather than stopping it entirely,
       so the user still knows that the process is active. */
    --_used-spinner-duration: 6s;
  }
}

Fallback-Strategien

Wenn @property unterstützt wird, wird die Strichanimation automatisch über initial-value des Attributs --progress-dash-animation eingefügt. In Browsern ohne @property-Unterstützung wird die Registrierung der Property ignoriert und die Animation wird auf eine einfache Drehung zurückgesetzt. Für dieses Fallback ist kein JavaScript erforderlich.

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

/* Fallback: use mask-image to create the ring */
@supports not (background-clip: border-area) {
  --clip-boundary: calc(100% - var(--thickness));
  mask-image: radial-gradient(
    farthest-side,
    transparent var(--clip-boundary),
    black var(--clip-boundary)
  );
  border: 0;
}