Eine grundlegende Übersicht darüber, wie Sie mit dem <progress>
-Element eine farbadaptive und barrierefreie Ladeleiste erstellen.
In diesem Beitrag möchte ich Ihnen zeigen, wie Sie mit dem <progress>
-Element eine farbadaptive und barrierefreie Ladeleiste erstellen können. Demo ansehen und Quellcode ansehen.
Wenn du lieber ein Video ansehen möchtest, findest du hier eine YouTube-Version dieses Beitrags:
Übersicht
Das Element
<progress>
bietet Nutzern visuelles und akustisches Feedback zum Abschluss. Dieses visuelle Feedback ist in folgenden Szenarien nützlich: Fortschritt durch ein Formular, Anzeige von Download- oder Uploadinformationen oder sogar Anzeige, dass der Fortschritt unbekannt ist, aber die Arbeit noch aktiv ist.
Bei dieser GUI-Challenge wurde das vorhandene HTML-Element <progress>
verwendet, um den Aufwand für die Barrierefreiheit zu verringern. Die Farben und Layouts gehen über die Anpassungsmöglichkeiten des integrierten Elements hinaus, um die Komponente zu modernisieren und besser in Designsysteme einzufügen.

Markieren & Zeichnen
Ich habe das <progress>
-Element in ein <label>
-Element eingeschlossen, um explizite Beziehungsattribute zugunsten einer impliziten Beziehung zu überspringen.
Außerdem habe ich ein übergeordnetes Element mit dem Ladezustand gekennzeichnet, damit Screenreader diese Informationen an den Nutzer weitergeben können.
<progress></progress>
Wenn kein value
vorhanden ist, ist der Fortschritt des Elements unbestimmt.
Das Attribut max
hat standardmäßig den Wert 1. Der Fortschritt liegt also zwischen 0 und 1. Wenn Sie max
beispielsweise auf 100 festlegen, wird der Bereich auf 0–100 gesetzt. Ich habe mich entschieden, die Grenzwerte 0 und 1 beizubehalten und Fortschrittswerte in 0,5 oder 50 % zu übersetzen.
Fortschritt mit Label
Bei einer impliziten Beziehung wird ein Fortschrittselement so in ein Label eingeschlossen:
<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>
-Tag eingeschlossen und es werden einige Formatierungen angewendet, damit er effektiv nicht auf dem Bildschirm angezeigt wird:
<label>
<span class="sr-only">Loading progress</span>
<progress></progress>
</label>
Mit dem folgenden begleitenden CSS 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;
}
Bereich, der vom Ladestatus betroffen ist
Wenn Sie eine gute Sehkraft haben, können Sie einen Fortschrittsindikator leicht mit zugehörigen Elementen und Seitenbereichen in Verbindung bringen. Für Nutzer mit Sehbeeinträchtigungen ist das jedoch nicht so einfach. Sie können dies verbessern, indem Sie das Attribut aria-busy
dem obersten Element zuweisen, das sich ändert, wenn das Laden abgeschlossen ist.
Geben 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>
Schalten Sie in JavaScript zu Beginn der Aufgabe aria-busy
auf true
und nach Abschluss auf false
um.
Hinzufügen von Aria-Attributen
Die implizite Rolle eines <progress>
-Elements ist progressbar
. Ich habe sie jedoch für Browser, die diese implizite Rolle nicht unterstützen, explizit angegeben. Außerdem habe ich das Attribut indeterminate
hinzugefügt, um das Element explizit in den Status „Unbekannt“ zu versetzen. Das ist eindeutiger, als wenn man feststellt, 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. Das ist wichtig für Screenreader, da der Nutzer so erfährt, wie weit der aktualisierte Fortschritt ist.
Stile
Das Fortschrittselement ist in Bezug auf die Gestaltung etwas kompliziert. 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 der Beschriftung ermöglichen. Es wurde ein spezieller Abschlussstatus hinzugefügt, der als nützliches, aber nicht erforderliches zusätzliches visuelles Signal dienen kann.
<progress>
Layout
Die Breite des Fortschrittselements bleibt unverändert, sodass es je nach Bedarf im Design verkleinert und vergrößert werden kann. Die integrierten Stile werden entfernt, indem appearance
und border
auf none
gesetzt werden. So kann das Element browserübergreifend normalisiert werden, 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 wissenschaftlicher Schreibweise ausgedrückt, um eine große Zahl darzustellen. Daher wird border-radius
immer gerundet. Dies entspricht 1000px
. Ich verwende diese Option gern, weil ich einen Wert verwenden möchte, der so groß ist, dass ich ihn einmal festlegen und dann vergessen kann. Außerdem ist er kürzer als 1000px
. Bei Bedarf lässt er sich auch ganz einfach noch größer machen: Ändern Sie einfach die 3 in eine 4. Dann entspricht 1e4px
10000px
.
overflow: hidden
wird verwendet und war schon immer umstritten. Das hat einige Dinge vereinfacht, z. B. mussten keine border-radius
-Werte an den Track und die Track-Füllelemente übergeben werden. Es bedeutete aber auch, dass keine untergeordneten Elemente des Fortschritts außerhalb des Elements vorhanden sein konnten. Eine weitere Iteration dieses benutzerdefinierten Fortschrittselements könnte ohne overflow: hidden
erfolgen. Das könnte einige Möglichkeiten für Animationen oder bessere Abschlussstatus eröffnen.
Vorgang abgeschlossen
CSS-Selektoren vergleichen den Maximalwert mit dem Wert. Wenn sie übereinstimmen, ist der Fortschritt abgeschlossen. Wenn der Vorgang abgeschlossen ist, wird ein Pseudoelement generiert und an das Ende des Fortschrittselements angehängt. So wird ein zusätzlicher visueller Hinweis auf den Abschluss gegeben.
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 stellt eigene Farben für das Fortschrittselement bereit, die sich mit nur einer CSS-Eigenschaft an helle und dunkle Designs anpassen lassen. Darauf lässt sich mit einigen speziellen browserspezifischen Selektoren aufbauen.
Helle und dunkle Browserstile
Um Ihre Website für ein adaptives <progress>
-Element für helle und dunkle Designs zu aktivieren, ist nur color-scheme
erforderlich.
progress {
color-scheme: light dark;
}
Farbe für Fortschrittsanzeige für einzelne Unterkunft
Verwenden Sie accent-color
, um ein <progress>
-Element zu tönen.
progress {
accent-color: rebeccapurple;
}
Die Hintergrundfarbe des Tracks ändert sich je nach accent-color
von hell zu dunkel. Der Browser sorgt für einen angemessenen Kontrast.
Vollständig benutzerdefinierte helle und dunkle Farben
Legen Sie zwei benutzerdefinierte Eigenschaften für das <progress>
-Element fest, eine für die Farbe des Tracks und eine für die Farbe des Trackfortschritts. Geben Sie in der Media-Anfrage prefers-color-scheme
neue Farbwerte für den Track und den Fortschritt des Tracks 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
Vorhin haben wir dem Element einen negativen Tabindex zugewiesen, damit es programmatisch fokussiert werden kann. Mit :focus-visible
können Sie den Fokus anpassen und den intelligenten Fokusringstil aktivieren. Bei einem Mausklick und Fokus wird der Fokusring nicht angezeigt, bei Tastaturklicks jedoch schon. Das YouTube-Video enthält weitere Informationen dazu.
progress:focus-visible {
outline-color: var(--_progress);
outline-offset: 5px;
}
Browserübergreifende benutzerdefinierte Stile
Passen Sie die Stile an, indem Sie die Teile eines <progress>
-Elements auswählen, die von den einzelnen Browsern bereitgestellt werden. Das „progress“-Element ist ein einzelnes Tag, besteht aber aus mehreren untergeordneten Elementen, die über CSS-Pseudoselektoren verfügbar gemacht werden. In den Chrome-Entwicklertools werden diese Elemente angezeigt, wenn Sie die Einstellung aktivieren:
- Klicken Sie mit der rechten Maustaste auf die Seite und wählen Sie Element untersuchen aus, um die Entwicklertools aufzurufen.
- Klicken Sie rechts oben im DevTools-Fenster auf das Zahnradsymbol für die Einstellungen.
- Suchen Sie unter der Überschrift Elemente das Kästchen Schatten-DOM des User-Agents anzeigen und aktivieren Sie es.
Safari- und Chromium-Stile
WebKit-basierte Browser wie Safari und Chromium stellen ::-webkit-progress-bar
und ::-webkit-progress-value
zur Verfügung, wodurch eine Teilmenge von CSS verwendet werden kann. Legen Sie background-color
vorerst mit den zuvor erstellten benutzerdefinierten Eigenschaften fest, die sich an helle und dunkle Designs anpassen.
/* Safari/Chromium */
progress[value]::-webkit-progress-bar {
background-color: var(--_track);
}
progress[value]::-webkit-progress-value {
background-color: var(--_progress);
}
Firefox-Stile
In Firefox wird der Pseudoselektor ::-moz-progress-bar
nur für das Element <progress>
verfügbar gemacht. Das bedeutet auch, dass wir den Track nicht direkt tönen können.
/* Firefox */
progress[value]::-moz-progress-bar {
background-color: var(--_progress);
}
In Firefox ist die Farbe des Tracks über accent-color
festgelegt, in iOS Safari ist der Track hellblau. Im dunklen Modus ist es dasselbe: Firefox hat einen dunklen Track, aber nicht die benutzerdefinierte Farbe, die wir festgelegt haben. In Webkit-basierten Browsern funktioniert es.
Animation
Bei der Verwendung von integrierten Browser-Pseudoselektoren ist oft nur eine begrenzte Anzahl von zulässigen CSS-Eigenschaften verfügbar.
Animation des sich füllenden Tracks
Das Hinzufügen einer Übergangseigenschaft zum inline-size
des Fortschrittselements funktioniert in Chromium, aber nicht in Safari. Firefox verwendet auch keine Übergangseigenschaft für das ::-moz-progress-bar
.
/* Chromium Only 😢 */
progress[value]::-webkit-progress-value {
background-color: var(--_progress);
transition: inline-size .25s ease-out;
}
:indeterminate
-Status animieren
Hier werde ich etwas kreativer, damit ich eine Animation einfügen kann. Für Chromium wird ein Pseudoelement erstellt und ein Farbverlauf angewendet, der in allen drei Browsern hin und her animiert wird.
Die benutzerdefinierten Eigenschaften
Benutzerdefinierte Eigenschaften sind für viele Dinge nützlich. Am liebsten verwende ich sie, um einem ansonsten magisch aussehenden CSS-Wert einen Namen zu geben. Im Folgenden sehen Sie eine ziemlich komplexe linear-gradient
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 tragen auch dazu bei, dass der Code DRY bleibt, da wir diese browserspezifischen Selektoren nicht gruppieren können.
Die Keyframes
Das Ziel ist eine Endlosanimation, die vor- und zurückläuft. Die Start- und End-Keyframes werden in CSS festgelegt. Für eine Animation, die immer wieder zum Ausgangspunkt zurückkehrt, ist nur ein Keyframe erforderlich: der mittlere Keyframe bei 50%
.
@keyframes progress-loading {
50% {
background-position: left;
}
}
Targeting auf die einzelnen Browser
Nicht in jedem Browser können Pseudo-Elemente für das <progress>
-Element erstellt oder die Fortschrittsanzeige animiert werden. Mehr Browser unterstützen die Animation des Tracks als die eines Pseudoelements. Daher verwende ich keine Pseudoelemente mehr als Grundlage, sondern animiere Balken.
Chromium-Pseudoelement
In Chromium ist das Pseudoelement ::after
zulässig, wenn es 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 eine wichtige Rolle beim <progress>
-Element. Damit wird der an das Element gesendete Wert gesteuert und dafür gesorgt, dass im Dokument genügend Informationen für Screenreader vorhanden sind.
const state = {
val: null
}
In der Demo gibt es Schaltflächen zum Steuern 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 die Funktion Zugriff auf das state
-Objekt, das Fortschrittselement und die <main>
-Zone hat.
const setProgress = () => {
}
Ladezustand für die Zone <main>
festlegen
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, entfernen Sie die Attribute value
und aria-valuenow
.null
Dadurch wird der Status von <progress>
auf „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 der Dezimalarithmetik in JavaScript beheben
Da ich mich für den Standardwert 1 als maximalen Fortschritt entschieden habe, verwenden die Inkrement- und Dekrementfunktionen der Demo Dezimalzahlen. JavaScript und andere Sprachen sind dafür nicht immer gut geeignet.
Hier ist eine roundDecimals()
-Funktion, mit der das Ergebnis der mathematischen Operation gekürzt wird:
const roundDecimals = (val, places) =>
+(Math.round(val + "e+" + places) + "e-" + places)
Runden Sie den Wert, damit er präsentiert 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
value
des 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
}
Fokus auf den Fortschritt legen
Wenn die Werte aktualisiert wurden, sehen Nutzer ohne Sehbeeinträchtigung die Änderung des Fortschritts. Nutzer von Screenreadern erhalten die Änderung jedoch noch nicht als Ansage. Fokussieren Sie das <progress>
-Element. Der Browser gibt die Aktualisierung dann aus.
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 wissen Sie, wie ich es gemacht habe. Wie würden Sie vorgehen? 🙂
Es gibt sicherlich ein paar Änderungen, die ich vornehmen würde, wenn ich noch einmal die Chance dazu hätte. Ich denke, dass die aktuelle Komponente verbessert werden kann und dass es möglich ist, eine Komponente ohne die Einschränkungen der Pseudoklassenstile des <progress>
-Elements zu erstellen. Es lohnt sich, sich das genauer anzusehen.
Wir möchten unsere Ansätze diversifizieren und alle Möglichkeiten kennenlernen, die das Web bietet.
Erstelle eine Demo, schick mir einen Tweet mit den Links und ich füge sie unten im Bereich „Community-Remixe“ hinzu.