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
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.](https://web.dev/static/articles/building/a-loading-bar-component/image/light-dark-tabs-each-br-cd04389a437d1.png?authuser=6&hl=de)
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;
}
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);
}
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;
}
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:
- Klicke mit der rechten Maustaste auf deine Seite und wähle Inspect Element aus, um die Entwicklertools aufzurufen.
- Klicken Sie oben rechts im Entwicklertools-Fenster auf das Zahnradsymbol für die Einstellungen.
- Aktivieren Sie unter der Überschrift Elemente das Kästchen User-Agent-Schatten-DOM anzeigen.
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);
}
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);
}
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:
- Das
value
-Attribut des<progress>
-Elements. - Das Attribut
aria-valuenow
. - 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()
}
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.