Eine Ladebalkenkomponente erstellen

Ein grundlegender Überblick darüber, wie du mit dem Element <progress> eine adaptive und barrierefreie Ladeleiste erstellst.

In diesem Beitrag möchte ich Ihnen zeigen, wie Sie mit dem Element <progress> eine adaptive und barrierefreie Ladeleiste erstellen können. Demo ansehen und den Quellcode ansehen

Demonstration von Hell und Dunkel, Unbestimmt, Zunahme und Vollständigkeit in Chrome.

Falls Sie Videos bevorzugen, finden Sie hier eine YouTube-Version dieses Beitrags:

Überblick

Das Element <progress> bietet Nutzern visuelles und akustisches Feedback zum Abschluss. Dieses visuelle Feedback ist nützlich für Szenarien wie den Fortschritt eines Formulars, das Anzeigen von Download- oder Upload-Informationen oder sogar den Nachweis, dass der Fortschritt unbekannt ist, die Arbeit aber noch aktiv ist.

Bei dieser GUI Challenge wurde das bestehende HTML-Element <progress> verwendet, um den Aufwand für die Barrierefreiheit zu verringern. Die Farben und Layouts erweitern die Grenzen der Anpassung für das integrierte Element, um die Komponente zu modernisieren und besser in Designsysteme zu integrieren.

Helle und dunkle Tabs in jedem Browser, die einen Überblick über das adaptive Symbol von oben nach unten geben: Safari, Firefox, Chrome.
Die Demo wird für Firefox, Safari, iOS Safari, Chrome und Android Chrome in hellen und dunklen Schemata dargestellt.

Markup

Ich habe das <progress>-Element in <label> eingebunden, damit ich die expliziten Beziehungsattribute zugunsten einer impliziten Beziehung überspringen kann. Ich habe auch ein übergeordnetes Element gekennzeichnet, das vom Ladestatus betroffen ist, damit Screenreader-Technologien diese Informationen an einen Nutzer zurückgeben können.

<progress></progress>

Wenn kein value vorhanden ist, ist der Fortschritt des Elements unbestimmt. Das Attribut max ist standardmäßig auf 1 gesetzt, der Fortschritt liegt also zwischen 0 und 1. Wenn du beispielsweise max auf 100 setzt, wird der Bereich auf 0–100 gesetzt. Ich blieb dabei innerhalb der Grenzen 0 und 1 und übersetze die Fortschrittswerte in 0, 5 oder 50%.

Fortschritt bei Label-Wrapping

In einer impliziten Beziehung wird ein Fortschrittselement wie folgt von einem Label umschlossen:

<label>Loading progress<progress></progress></label>

In meiner Demo habe ich mich dafür entschieden, das Label nur für Screenreader anzugeben. Dazu wird der Labeltext in ein <span>-Element eingeschlossen und Stile darauf angewendet, damit es effektiv außerhalb des Bildschirms platziert wird:

<label>
  <span class="sr-only">Loading progress</span>
  <progress></progress>
</label>

Mit folgendem CSS-Code von WebAIM:

.sr-only {
  clip: rect(1px, 1px, 1px, 1px);
  clip-path: inset(50%);
  height: 1px;
  width: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
}

Screenshot der Entwicklertools mit dem Bildschirmelement, das nur für den Bildschirm bereit ist.

Vom Ladefortschritt betroffene Bereiche

Bei einem gesunden Sehvermögen kann es leicht sein, eine Fortschrittsanzeige mit zugehörigen Elementen und Seitenbereichen zu verknüpfen. Für sehbehinderte Nutzer ist es jedoch nicht so klar. Sie können dies verbessern, indem Sie das Attribut aria-busy dem obersten Element zuweisen, das sich nach dem Laden ändert. Zeigen Sie außerdem mit aria-describedby eine Beziehung zwischen dem Fortschritt und der Ladezone an.

<main id="loading-zone" aria-busy="true">
  …
  <progress aria-describedby="loading-zone"></progress>
</main>

In JavaScript wechseln Sie zu Beginn der Aufgabe von aria-busy zu true und dann zu false.

Hinzufügungen von Aria-Attributen

Die implizite Rolle eines <progress>-Elements ist zwar progressbar, aber ich habe es für Browser ohne diese implizite Rolle explizit gemacht. Außerdem habe ich das Attribut indeterminate hinzugefügt, um das Element explizit in den Zustand „Unbekannt“ zu versetzen. Dies ist deutlicher, als zu beobachten, dass für das Element kein value festgelegt ist.

<label>
  Loading 
  <progress 
    indeterminate 
    role="progressbar" 
    aria-describedby="loading-zone"
    tabindex="-1"
  >unknown</progress>
</label>

Verwenden Sie tabindex="-1", um das Fortschrittselement über JavaScript fokussierbar zu machen. Dies ist für die Screenreader-Technologie wichtig, da sich der Fortschritt bei Änderungen des Fortschritts fokussiert. Dadurch wird dem Nutzer mitgeteilt, wie weit der aktualisierte Fortschritt bereits erreicht ist.

Stile

Das Progress-Element ist etwas komplizierter. Integrierte HTML-Elemente haben besondere verborgene Teile, die schwer auszuwählen sind und oft nur eine begrenzte Anzahl von Eigenschaften bieten, die festgelegt werden können.

Layout

Die Layoutstile sollen eine gewisse Flexibilität bei der Größe und Labelposition des Fortschrittselements ermöglichen. Es wird ein spezieller Abschlussstatus hinzugefügt, der ein nützlicher, aber nicht erforderlicher zusätzlicher visueller Hinweis sein kann.

<progress>-Layout

Die Breite des Fortschrittselements bleibt unverändert, sodass es mit dem für das Design erforderlichen Platz verkleinern und wachsen kann. Die integrierten Stile werden entfernt, indem appearance und border auf none gesetzt werden. Dies geschieht, damit das Element für verschiedene Browser normalisiert werden kann, da jeder Browser seine eigenen Stile für sein Element hat.

progress {
  --_track-size: min(10px, 1ex);
  --_radius: 1e3px;

  /*  reset  */
  appearance: none;
  border: none;

  position: relative;
  height: var(--_track-size);
  border-radius: var(--_radius);
  overflow: hidden;
}

Der Wert von 1e3px für _radius verwendet die Notation wissenschaftlicher Zahlen, um eine große Zahl auszudrücken, sodass border-radius immer gerundet wird. Dies entspricht 1000px. Ich verwende diese Funktion gerne, weil mein Ziel darin besteht, einen Wert zu verwenden, der groß genug ist, dass ich ihn setzen und vergessen kann (und das ist kürzer als 1000px). Bei Bedarf kann er auch einfach noch größer werden: Ändern Sie einfach die 3 in eine 4, dann entspricht 1e4px 10000px.

overflow: hidden“ wurde in einem strittigen Stil verwendet. Dadurch wurden einige Dinge einfacher gemacht, z. B. musste keine border-radius-Werte an den Track übergeben und Füllelemente verfolgt werden. Es bedeutete aber auch, dass keine untergeordneten Elemente des Fortschritts außerhalb des Elements angesiedelt sein konnten. Eine weitere Iteration dieses benutzerdefinierten Fortschrittselements könnte ohne overflow: hidden durchgeführt werden, was möglicherweise einige Möglichkeiten für Animationen oder bessere Abschlussstatus bietet.

Vorgang abgeschlossen

CSS-Selektoren erledigen hier die schwierige Arbeit, indem sie den Maximalwert mit dem Wert vergleichen. Wenn sie übereinstimmen, ist der Fortschritt abgeschlossen. Nach Abschluss wird ein Pseudoelement generiert und an das Ende des progress -Elements angehängt, das einen schönen zusätzlichen visuellen Hinweis auf den Abschluss bietet.

progress:not([max])[value="1"]::before,
progress[max="100"][value="100"]::before {
  content: "✓";
  
  position: absolute;
  inset-block: 0;
  inset-inline: auto 0;
  display: flex;
  align-items: center;
  padding-inline-end: max(calc(var(--_track-size) / 4), 3px);

  color: white;
  font-size: calc(var(--_track-size) / 1.25);
}

Screenshot des Ladebalkens bei 100% mit einem Häkchen am Ende

Farbe

Der Browser verwendet für das Fortschrittselement eigene Farben und ist mit nur einer CSS-Eigenschaft an hell und dunkel angepasst. Dies kann mit einigen speziellen browserspezifischen Selektoren aufgebaut werden.

Helle und dunkle Browserstile

Wenn Sie für Ihre Website ein adaptives <progress>-Element in dunkler und heller Form verwenden möchten, ist lediglich color-scheme erforderlich.

progress {
  color-scheme: light dark;
}

Füllfarbe des Fortschritts einer einzelnen Unterkunft

Verwende accent-color, um ein <progress>-Element zu färben.

progress {
  accent-color: rebeccapurple;
}

Beachten Sie, dass sich die Hintergrundfarbe des Tracks abhängig von accent-color von hell zu dunkel ändert. Der Browser sorgt für einen angemessenen Kontrast: ziemlich übersichtlich.

Vollständig benutzerdefinierte helle und dunkle Farben

Legen Sie zwei benutzerdefinierte Eigenschaften für das Element <progress> fest, eine für die Farbe des Tracks und die andere für die Farbe des Track-Fortschritts. Geben Sie in der Medienabfrage prefers-color-scheme neue Farbwerte für den Track an und verfolgen Sie den Fortschritt.

progress {
  --_track: hsl(228 100% 90%);
  --_progress: hsl(228 100% 50%);
}

@media (prefers-color-scheme: dark) {
  progress {
    --_track: hsl(228 20% 30%);
    --_progress: hsl(228 100% 75%);
  }
}

Fokusstile

Wir haben dem Element bereits einen negativen Tabindex zugewiesen, damit es programmatisch fokussiert werden kann. Mit :focus-visible können Sie den Fokus anpassen, um den intelligenten Fokusring zu aktivieren. Bei Mausklicks und dem Fokus wird nicht der Fokusring angezeigt, Klicks über die Tastatur jedoch schon. Im YouTube-Video wird dies ausführlicher beschrieben. Es lohnt sich, sie sich anzusehen.

progress:focus-visible {
  outline-color: var(--_progress);
  outline-offset: 5px;
}

Screenshot der Ladeleiste mit einem Fokusring darum Alle Farben stimmen überein.

Benutzerdefinierte Stile für verschiedene Browser

Passen Sie die Stile an, indem Sie die Teile eines <progress>-Elements auswählen, die in jedem Browser angezeigt werden. Das progress-Element ist ein einzelnes Tag, das jedoch aus einigen untergeordneten Elementen besteht, die über CSS-Pseudoselektoren angezeigt werden. Die Chrome-Entwicklertools zeigen folgende Elemente an, wenn Sie diese Einstellung aktivieren:

  1. Klicke mit der rechten Maustaste auf deine Seite und wähle Inspect Element aus, um die Entwicklertools aufzurufen.
  2. Klicken Sie oben rechts im Entwicklertools-Fenster auf das Zahnradsymbol für die Einstellungen.
  3. Aktivieren Sie unter der Überschrift Elemente das Kästchen User-Agent-Schatten-DOM anzeigen.

Screenshot dazu, wo in den Entwicklertools das Verfügbarmachen des User-Agent-Shadow-DOM aktiviert werden kann.

Safari- und Chromium-Stile

WebKit-basierte Browser wie Safari und Chromium bieten ::-webkit-progress-bar und ::-webkit-progress-value an, wodurch ein Teil von CSS verwendet werden kann. Legen Sie background-color vorerst mit den zuvor erstellten benutzerdefinierten Eigenschaften fest, die sich an hell und dunkel anpassen.

/*  Safari/Chromium  */
progress[value]::-webkit-progress-bar {
  background-color: var(--_track);
}

progress[value]::-webkit-progress-value {
  background-color: var(--_progress);
}

Screenshot mit den inneren Elementen des „progress“-Elements

Firefox-Stile

Firefox zeigt den Pseudoselektor ::-moz-progress-bar nur für das Element <progress> an. Das bedeutet auch, dass wir den Titel nicht direkt färben können.

/*  Firefox  */
progress[value]::-moz-progress-bar {
  background-color: var(--_progress);
}

Screenshot von Firefox und Informationen dazu, wo Sie die Bestandteile des Fortschrittselements finden

Screenshot der Debugging Corner, in dem die Ladeleiste in Safari, iOS Safari, Firefox, Chrome und Chrome unter Android angezeigt wird

Beachten Sie, dass die Track-Farbe von Firefox in Firefox auf accent-color eingestellt ist, während die Spur von iOS Safari eine hellblaue Spur hat. Im dunklen Modus ist das Gleiche: Firefox hat einen dunklen Spuren, aber nicht die von uns festgelegte benutzerdefinierte Farbe, und es funktioniert in WebKit-basierten Browsern.

Animation

Wenn Sie mit im Browser integrierten Pseudoselektoren arbeiten, steht dafür häufig nur eine begrenzte Anzahl zulässiger CSS-Eigenschaften zur Verfügung.

Animation der Strecke, die sich füllt

Das Hinzufügen eines Übergangs zum inline-size des Fortschrittselements funktioniert in Chromium, aber nicht in Safari. Firefox verwendet auch keine Übergangseigenschaft für ::-moz-progress-bar.

/*  Chromium Only 😢  */
progress[value]::-webkit-progress-value {
  background-color: var(--_progress);
  transition: inline-size .25s ease-out;
}

Status :indeterminate animieren

Hier werde ich etwas kreativer, damit ich eine Animation bereitstellen kann. Für Chromium wird ein Pseudoelement erstellt und ein Farbverlauf angewendet, der für alle drei Browser animiert wird.

Benutzerdefinierte Eigenschaften

Benutzerdefinierte Eigenschaften eignen sich für viele Dinge, aber einer meiner Favoriten ist es, einem ansonsten magisch anmutenden CSS-Wert einen Namen zu geben. Im Folgenden finden Sie ein ziemlich komplexes linear-gradient mit einem hübschen Namen. Sein Zweck und seine Anwendungsfälle sind klar nachvollziehbar.

progress {
  --_indeterminate-track: linear-gradient(to right,
    var(--_track) 45%,
    var(--_progress) 0%,
    var(--_progress) 55%,
    var(--_track) 0%
  );
  --_indeterminate-track-size: 225% 100%;
  --_indeterminate-track-animation: progress-loading 2s infinite ease;
}

Benutzerdefinierte Eigenschaften tragen auch dazu bei, dass der Code DRY bleibt, da wir diese browserspezifischen Selektoren auch hier nicht gruppieren können.

Die Keyframes

Das Ziel ist eine unendliche Animation, die hin und her wechselt. Die Start- und End-Keyframes werden in CSS festgelegt. Sie benötigen nur einen Keyframe – den mittleren Keyframe unter 50% –, um eine Animation zu erstellen, die immer wieder zum Ausgangspunkt zurückkehrt.

@keyframes progress-loading {
  50% {
    background-position: left; 
  }
}

Targeting auf jeden Browser

Nicht in jedem Browser können Pseudoelemente im <progress>-Element selbst erstellt oder die Fortschrittsanzeige animiert werden. Die Animation des Tracks wird von mehr Browsern unterstützt als von einem Pseudoelement. Daher steige ich von Pseudoelementen als Basis auf Animationsbalken um.

Chromium-Pseudoelement

Chromium lässt das Pseudoelement ::after zu, das mit einer Position verwendet wird, um das Element abzudecken. Die unbestimmten benutzerdefinierten Eigenschaften werden verwendet und die Rück- und Rückanimation funktioniert sehr gut.

progress:indeterminate::after {
  content: "";
  inset: 0;
  position: absolute;
  background: var(--_indeterminate-track);
  background-size: var(--_indeterminate-track-size);
  background-position: right; 
  animation: var(--_indeterminate-track-animation);
}
Safari-Fortschrittsanzeige

In Safari werden die benutzerdefinierten Eigenschaften und eine Animation auf die Fortschrittsanzeige des Pseudoelements angewendet:

progress:indeterminate::-webkit-progress-bar {
  background: var(--_indeterminate-track);
  background-size: var(--_indeterminate-track-size);
  background-position: right; 
  animation: var(--_indeterminate-track-animation);
}
Firefox-Fortschrittsanzeige

In Firefox werden die benutzerdefinierten Eigenschaften und eine Animation auch auf die Fortschrittsanzeige des Pseudoelements angewendet:

progress:indeterminate::-moz-progress-bar {
  background: var(--_indeterminate-track);
  background-size: var(--_indeterminate-track-size);
  background-position: right; 
  animation: var(--_indeterminate-track-animation);
}

JavaScript

JavaScript spielt beim <progress>-Element eine wichtige Rolle. Er steuert den an das Element gesendeten Wert und sorgt dafür, dass im Dokument genügend Informationen für Screenreader vorhanden sind.

const state = {
  val: null
}

Die Demo enthält Schaltflächen zur Steuerung des Fortschritts. Sie aktualisieren state.val und rufen dann eine Funktion zum Aktualisieren des DOM auf.

document.querySelector('#complete').addEventListener('click', e => {
  state.val = 1
  setProgress()
})

setProgress()

In dieser Funktion findet die UI/UX-Orchestrierung statt. Erstellen Sie zuerst eine setProgress()-Funktion. Es sind keine Parameter erforderlich, da sie Zugriff auf das state-Objekt, das Fortschrittselement und die <main>-Zone hat.

const setProgress = () => {
  
}

Ladestatus für die Zone <main> festlegen

Je nachdem, ob der Fortschritt abgeschlossen ist oder nicht, muss das zugehörige <main>-Element das Attribut aria-busy aktualisiert werden:

const setProgress = () => {
  zone.setAttribute('aria-busy', state.val < 1)
}

Attribute löschen, wenn der Ladebetrag unbekannt ist

Wenn der Wert unbekannt oder nicht konfiguriert ist, entfernen Sie bei dieser Verwendung null die Attribute value und aria-valuenow. Dadurch wird <progress> in „Unbestimmt“ geändert.

const setProgress = () => {
  zone.setAttribute('aria-busy', state.val < 1)

  if (state.val === null) {
    progress.removeAttribute('aria-valuenow')
    progress.removeAttribute('value')
    progress.focus()
    return
  }
}

Probleme mit Dezimalzahlen in JavaScript beheben

Da ich mich für den Standardwert von 1 für den Fortschritt bleibe, verwenden die Demo-Funktionen für Inkrementierung und Dekrement Dezimalzahlen. JavaScript und andere Sprachen sind dabei nicht immer gut. Hier sehen Sie eine roundDecimals()-Funktion, mit der der Überschuss des mathematischen Ergebnisses entfernt wird:

const roundDecimals = (val, places) =>
  +(Math.round(val + "e+" + places)  + "e-" + places)

Runden Sie den Wert, damit er angezeigt werden kann und lesbar ist:

const setProgress = () => {
  zone.setAttribute('aria-busy', state.val < 1)

  if (state.val === null) {
    progress.removeAttribute('aria-valuenow')
    progress.removeAttribute('value')
    progress.focus()
    return
  }

  const val = roundDecimals(state.val, 2)
  const valPercent = val * 100 + "%"
}

Wert für Screenreader und Browserstatus festlegen

Der Wert wird an drei Stellen im DOM verwendet:

  1. Das value-Attribut des <progress>-Elements.
  2. Das Attribut aria-valuenow.
  3. Der innere Textinhalt <progress>.
const setProgress = () => {
  zone.setAttribute('aria-busy', state.val < 1)

  if (state.val === null) {
    progress.removeAttribute('aria-valuenow')
    progress.removeAttribute('value')
    progress.focus()
    return
  }

  const val = roundDecimals(state.val, 2)
  const valPercent = val * 100 + "%"

  progress.value = val
  progress.setAttribute('aria-valuenow', valPercent)
  progress.innerText = valPercent
}

Den Fortschritt in den Fokus rücken

Wenn die Werte aktualisiert wurden, sehen sehende Nutzer die Fortschrittsänderung, Nutzer von Screenreadern wurden jedoch noch nicht über die Änderung informiert. Setzen Sie den Fokus auf das <progress>-Element. Der Browser kündigt dann das Update an.

const setProgress = () => {
  zone.setAttribute('aria-busy', state.val < 1)

  if (state.val === null) {
    progress.removeAttribute('aria-valuenow')
    progress.removeAttribute('value')
    progress.focus()
    return
  }

  const val = roundDecimals(state.val, 2)
  const valPercent = val * 100 + "%"

  progress.value = val
  progress.setAttribute('aria-valuenow', valPercent)
  progress.innerText = valPercent

  progress.focus()
}

Screenshot der Voice Over-App unter Mac OS, der dem Nutzer den Fortschritt des Ladebalkens vorliest.

Fazit

Jetzt, wo du weißt, wie ich es gemacht habe, wie würdest du... ‽ 🙂

Es gibt sicherlich ein paar Änderungen, die ich gerne vornehmen würde, wenn ich noch eine Gelegenheit dazu hätte. Ich denke, es ist Raum, die aktuelle Komponente zu bereinigen, und Raum, eine ohne die Stilbeschränkungen der Pseudoklasse des <progress>-Elements zu erstellen. Es lohnt sich!

Lassen Sie uns unsere Herangehensweisen diversifizieren und alle Möglichkeiten kennenlernen, wie wir das Web entwickeln können.

Erstelle eine Demo, Tweets an mich und füge sie unten im Abschnitt zu Community-Remixen hinzu.

Community-Remixe