Eine grundlegende Übersicht darüber, wie Sie mit dem <progress>-Element einen farblich anpassbaren und barrierefreien Ladebalken erstellen.
In diesem Beitrag möchte ich Ihnen zeigen, wie Sie mit dem <progress>-Element eine farblich anpassbare und barrierefreie Ladeleiste erstellen können. Demo ausprobieren und Quelle ansehen
Wenn Sie sich lieber ein Video ansehen, finden Sie hier eine YouTube-Version dieses Beitrags:
Übersicht
Das Element <progress> bietet Nutzern visuelles und akustisches Feedback zum Abschluss. Dieses visuelle Feedback ist in verschiedenen Szenarien nützlich, z. B. beim Ausfüllen eines Formulars, beim Anzeigen von Download- oder Upload-Informationen oder auch, wenn der Fortschritt unbekannt ist, aber die Arbeit noch aktiv ist.
In dieser GUI Challenge wurde das vorhandene HTML-Element <progress> verwendet, um den Aufwand für die Barrierefreiheit zu reduzieren. Die Farben und Layouts erweitern die Anpassungsmöglichkeiten für das integrierte Element, um die Komponente zu modernisieren und besser in Designsysteme einzubinden.
Markieren & Zeichnen
Ich habe mich dafür entschieden, das <progress>-Element in ein <label> einzubetten, damit ich die expliziten Beziehungsattribute zugunsten einer impliziten Beziehung überspringen kann.
Ich habe auch ein übergeordnetes Element, das vom Ladestatus betroffen ist, mit einem Label versehen, damit Screenreader-Technologien diese Informationen an Nutzer weitergeben können.
<progress></progress>
Wenn kein value vorhanden ist, ist der Fortschritt des Elements unbestimmt.
Das Attribut max ist standardmäßig auf 1 festgelegt, sodass der Fortschritt zwischen 0 und 1 liegt. Wenn Sie beispielsweise max auf 100 festlegen, wird der Bereich auf 0–100 festgelegt. Ich habe mich entschieden, innerhalb der Grenzen von 0 und 1 zu bleiben und die Fortschrittswerte in 0,5 oder 50 % zu übersetzen.
Fortschritt mit Label
In einer impliziten Beziehung wird ein Fortschrittselement von einem Label umschlossen, wie hier:
<label>Loading progress<progress></progress></label>
In meiner Demo habe ich das Label Nur für Screenreader ausgewählt.
Dazu wird der Labeltext in ein <span>-Element eingeschlossen und mit einigen Stilen versehen, sodass er effektiv außerhalb des Bildschirms liegt:
<label>
<span class="sr-only">Loading progress</span>
<progress></progress>
</label>
Mit dem folgenden zugehörigen 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;
}

Vom Ladefortschritt betroffener Bereich
Wenn Sie gut sehen können, ist es einfach, eine Fortschrittsanzeige mit zugehörigen Elementen und Seitenbereichen zu verknüpfen. Für sehbehinderte Nutzer ist das jedoch nicht so einfach. Sie können dies verbessern, indem Sie das Attribut aria-busy dem obersten Element zuweisen, das sich nach Abschluss des Ladevorgangs ändert.
Außerdem solltest du mit aria-describedby eine Beziehung zwischen dem Fortschritt und der Ladezone angeben.
<main id="loading-zone" aria-busy="true">
…
<progress aria-describedby="loading-zone"></progress>
</main>
Schalten Sie in JavaScript zu Beginn der Aufgabe aria-busy auf true und nach Abschluss auf false um.
Hinzugefügte Aria-Attribute
Die implizite Rolle eines <progress>-Elements ist progressbar. Ich habe sie jedoch explizit für Browser angegeben, die diese implizite Rolle nicht haben. Ich habe auch das Attribut indeterminate hinzugefügt, um das Element explizit in den Status „Unbekannt“ zu versetzen. Das ist klarer, als wenn 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. Das ist wichtig für Screenreader-Technologie, da die Fokussierung auf den Fortschritt bei Änderungen dem Nutzer mitteilt, wie weit der aktualisierte Fortschritt fortgeschritten ist.
Stile
Das Fortschrittselement ist beim Styling etwas knifflig. Integrierte HTML-Elemente haben spezielle 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 des Fortschrittselements und der Position des Labels ermöglichen. Es wird ein spezieller Abschlussstatus hinzugefügt, der ein nützlicher, aber nicht erforderlicher zusätzlicher visueller Hinweis sein kann.
Layout: <progress>
Die Breite des Fortschrittselements bleibt unverändert, sodass es je nach Platzbedarf im Design verkleinert oder vergrößert werden kann. Die integrierten Stile werden entfernt, indem appearance und border auf none gesetzt werden. Dies geschieht, damit das Element browserübergreifend normalisiert werden kann, da jeder Browser eigene Stile für das 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 wird in der wissenschaftlichen Zahlenschreibweise angegeben, um eine große Zahl auszudrücken. Daher wird border-radius immer gerundet. Dies entspricht 1000px. Ich verwende diesen Wert gerne, weil er groß genug ist, dass ich ihn festlegen und dann vergessen kann (und er ist kürzer zu schreiben als 1000px). Außerdem lässt er sich bei Bedarf leicht vergrößern: Ändern Sie einfach die 3 in eine 4, dann entspricht 1e4px 10000px.
overflow: hidden wird verwendet und ist ein umstrittener Stil. Das hat einiges vereinfacht, z. B. dass keine border-radius-Werte an die Track- und Track-Füllelemente übergeben werden mussten. Es bedeutete aber auch, dass keine untergeordneten Elemente des Fortschritts außerhalb des Elements existieren konnten. Eine weitere Iteration dieses benutzerdefinierten Fortschrittselements könnte ohne overflow: hidden erfolgen und möglicherweise einige Möglichkeiten für Animationen oder bessere Abschlusszustände eröffnen.
Vorgang abgeschlossen
CSS-Selektoren übernehmen hier die schwierige Aufgabe, den Maximalwert mit dem Wert zu vergleichen. Wenn sie übereinstimmen, ist der Fortschritt abgeschlossen. Wenn der Vorgang abgeschlossen ist, wird ein Pseudoelement generiert und am Ende des Fortschrittselements angehängt. Das ist ein schöner zusätzlicher visueller Hinweis auf den Abschluss.
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);
}

Farbe
Der Browser verwendet eigene Farben für das Fortschrittselement und passt sich mit nur einer CSS-Property an helle und dunkle Designs an. Dies kann mit einigen speziellen browserspezifischen Selektoren erweitert werden.
Helle und dunkle Browserdesigns
Wenn ihr für eure Website ein <progress>-Element aktivieren möchtet, das sich an den dunklen und hellen Modus anpasst, ist color-scheme alles, was ihr braucht.
progress {
color-scheme: light dark;
}
Füllfarbe für den Fortschritt einer einzelnen Unterkunft
Verwenden Sie accent-color, um ein <progress>-Element einzufärben.
progress {
accent-color: rebeccapurple;
}
Die Hintergrundfarbe des Tracks ändert sich je nach accent-color von hell zu dunkel. Der Browser sorgt für einen guten Kontrast – ziemlich cool.
Vollständig benutzerdefinierte helle und dunkle Farben
Lege zwei benutzerdefinierte Eigenschaften für das <progress>-Element fest: eine für die Farbe der Leiste und eine für die Farbe des Fortschritts. Geben Sie in der Medienabfrage prefers-color-scheme neue Farbwerte für den Track und den Track-Fortschritt an.
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
Zuvor haben wir dem Element einen negativen Tabindex zugewiesen, damit es programmatisch fokussiert werden kann. Verwenden Sie :focus-visible, um den Fokus anzupassen und den intelligenteren Fokusringstil zu aktivieren. Dadurch wird der Fokusring bei Mausklicks und Fokus nicht angezeigt, bei Tastaturklicks jedoch schon. Im YouTube-Video wird dieses Thema ausführlicher behandelt. Es lohnt sich, es anzusehen.
progress:focus-visible {
outline-color: var(--_progress);
outline-offset: 5px;
}

Benutzerdefinierte Stile in verschiedenen Browsern
Passen Sie die Stile an, indem Sie die Teile eines <progress>-Elements auswählen, die von den einzelnen Browsern bereitgestellt werden. Das Fortschrittselement ist ein einzelnes Tag, besteht aber aus einigen untergeordneten Elementen, die über CSS-Pseudoselektoren verfügbar gemacht werden. In den Chrome-Entwicklertools werden diese Elemente angezeigt, wenn Sie die folgende Einstellung aktivieren:
- Rechtsklicken Sie auf Ihre Seite und wählen Sie Element untersuchen aus, um die Entwicklertools zu öffnen.
- Klicken Sie oben rechts im DevTools-Fenster auf das Zahnradsymbol für die Einstellungen.
- Suchen Sie unter der Überschrift Elemente das Kästchen User-Agent-Shadow-DOM anzeigen und aktivieren Sie es.

Safari- und Chromium-Stile
WebKit-basierte Browser wie Safari und Chromium stellen ::-webkit-progress-bar und ::-webkit-progress-value bereit, die die Verwendung einer Teilmenge von CSS ermöglichen. Legen Sie background-color vorerst mit den zuvor erstellten benutzerdefinierten Eigenschaften fest, die sich an den hellen und dunklen Modus anpassen.
/* Safari/Chromium */
progress[value]::-webkit-progress-bar {
background-color: var(--_track);
}
progress[value]::-webkit-progress-value {
background-color: var(--_progress);
}

Firefox-Stile
Firefox stellt den Pseudoselektor ::-moz-progress-bar nur für das <progress>-Element bereit. Das bedeutet auch, dass wir den Track nicht direkt einfärben können.
/* Firefox */
progress[value]::-moz-progress-bar {
background-color: var(--_progress);
}


In Firefox ist die Farbe der Spur auf accent-color festgelegt, während iOS Safari eine hellblaue Spur hat. Im dunklen Modus ist es genauso: Firefox hat eine dunkle Spur, aber nicht die von uns festgelegte benutzerdefinierte Farbe. In Webkit-basierten Browsern funktioniert es.
Animation
Wenn Sie mit integrierten Pseudoselektoren des Browsers arbeiten, ist die Auswahl an zulässigen CSS-Eigenschaften oft begrenzt.
Animation des sich füllenden Rings
Das Hinzufügen eines Übergangs zum inline-size des Fortschrittselements funktioniert für Chromium, aber nicht für Safari. Firefox verwendet auch keine Übergangs-Property für ::-moz-progress-bar.
/* Chromium Only 😢 */
progress[value]::-webkit-progress-value {
background-color: var(--_progress);
transition: inline-size .25s ease-out;
}
Status „:indeterminate“ wird animiert
Hier werde ich etwas kreativer, um eine Animation zu erstellen. Für Chromium wird ein Pseudoelement erstellt und ein Farbverlauf angewendet, der für alle drei Browser hin und her animiert wird.
Die benutzerdefinierten Eigenschaften
Benutzerdefinierte Eigenschaften sind für viele Dinge nützlich. Eine meiner Lieblingsfunktionen ist es, einem ansonsten magisch aussehenden CSS-Wert einfach einen Namen zu geben. Im Folgenden finden Sie eine ziemlich komplexe linear-gradient, aber mit einem schönen Namen. Zweck und Anwendungsfälle sind klar verständlich.
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 helfen auch, den Code DRY zu halten, da wir diese browserspezifischen Selektoren nicht gruppieren können.
Die Keyframes
Das Ziel ist eine unendliche Animation, die hin und her läuft. Die Start- und End-Keyframes werden in CSS festgelegt. Es ist nur ein Keyframe erforderlich, der mittlere Keyframe bei 50%, um eine Animation zu erstellen, die immer wieder zum Ausgangspunkt zurückkehrt.
@keyframes progress-loading {
50% {
background-position: left;
}
}
Targeting auf die einzelnen Browser
Nicht jeder Browser erlaubt die Erstellung von Pseudoelementen für das <progress>-Element selbst oder die Animation der Fortschrittsanzeige. Mehr Browser unterstützen die Animation des Tracks als ein Pseudoelement. Daher verwende ich Pseudoelemente als Grundlage und animiere Balken.
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 Animation 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);
}
Fortschrittsanzeige in Safari
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 Wert, der an das Element gesendet wird, und sorgt dafür, dass im Dokument genügend Informationen für Screenreader vorhanden sind.
const state = {
val: null
}
In der Demo gibt es Schaltflächen, mit denen der Fortschritt gesteuert werden kann. 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 erfolgt die UI/UX-Orchestrierung. Erstellen Sie zuerst eine setProgress()-Funktion. Es sind keine Parameter erforderlich, da Zugriff auf das Objekt state, das Fortschrittselement und die Zone <main> besteht.
const setProgress = () => {
}
Ladestatus für die Zone <main> wird festgelegt
Je nachdem, ob der Fortschritt abgeschlossen ist oder nicht, muss das zugehörige <main>-Element mit dem 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 festgelegt ist, null in dieser Verwendung, entfernen Sie die Attribute value und aria-valuenow. Dadurch wird der Status von <progress> auf „Unbestimmt“ gesetzt.
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 der Dezimalrechnung in JavaScript beheben
Da ich mich für den Standardwert 1 für den Fortschritt entschieden habe, werden in den Demo-Funktionen zum Erhöhen und Verringern Dezimalzahlen verwendet. JavaScript und andere Sprachen sind darin nicht immer gut.
Hier ist eine roundDecimals()-Funktion, mit der die überflüssigen Stellen des mathematischen Ergebnisses entfernt werden:
const roundDecimals = (val, places) =>
+(Math.round(val + "e+" + places) + "e-" + places)
Runden Sie den Wert, damit er dargestellt 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:
- Das Attribut
valuedes Elements<progress>. - Das Attribut
aria-valuenow. - Der innere Textinhalt von
<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
}
Fortschritt im Fokus
Wenn die Werte aktualisiert werden, sehen sehende Nutzer die Änderung des Fortschritts, aber Screenreader-Nutzer werden noch nicht über die Änderung informiert. Fokussieren Sie das <progress>-Element und der Browser kündigt die Aktualisierung 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()
}

Fazit
Jetzt, da ihr wisst, wie ich es gemacht habe, wie würdet ihr es machen? 🙂
Es gibt sicherlich ein paar Dinge, die ich ändern würde, wenn ich die Chance dazu hätte. Ich denke, dass die aktuelle Komponente noch verbessert werden kann und dass es sich lohnt, eine Komponente ohne die Einschränkungen des Pseudoklassenstils des <progress>-Elements zu erstellen. Es lohnt sich, das auszuprobieren.
Lassen Sie uns unsere Ansätze diversifizieren und alle Möglichkeiten kennenlernen, wie wir im Web entwickeln können.
Erstellen Sie eine Demo, twittern Sie mir Links und ich füge sie dem Abschnitt „Community-Remixe“ unten hinzu.