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. Probieren Sie die Demo aus und sehen Sie sich die Quelle an.
Wenn du lieber ein Video ansiehst, findest du hier eine YouTube-Version dieses Beitrags:
Übersicht
Das Element <progress>
gibt Nutzern visuelles und akustisches Feedback zum Abschluss. Dieses visuelle Feedback ist in folgenden Fällen hilfreich: Fortschritt beim Ausfüllen eines Formulars, Anzeige von Informationen zum Herunterladen oder Hochladen oder auch, wenn der Fortschritt nicht bekannt 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.
Markieren & Zeichnen
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 diese Informationen an einen 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 festgelegt. Ich habe mich dafür entschieden, die Grenzen von 0 und 1 einzuhalten und Fortschrittswerte in 0,5 oder 50 % umzuwandeln.
Fortschritt mit Label
In einer impliziten Beziehung wird ein Fortschrittselement wie folgt von einem Label umschlossen:
<label>Loading progress<progress></progress></label>
In meiner Demo habe ich das Label nur für Screenreader eingefügt.
Dazu fügen Sie den Labeltext in ein <span>
ein und wenden einige Stile darauf an, damit er nicht mehr auf dem Bildschirm zu sehen ist:
<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 Ladevorgang betroffenes Gebiet
Wenn Sie gut sehen, ist es oft einfach, eine Fortschrittsanzeige mit zugehörigen Elementen und Seitenbereichen zu verknüpfen. Für sehbehinderte Nutzer ist das jedoch nicht so klar. Sie können das Problem beheben, indem Sie das Attribut aria-busy
dem obersten Element zuweisen, das sich ändern soll, wenn der Ladevorgang abgeschlossen ist.
Geben Sie außerdem mit aria-describedby
den Zusammenhang zwischen dem Fortschritt und der Ladezone an.
<main id="loading-zone" aria-busy="true">
…
<progress aria-describedby="loading-zone"></progress>
</main>
Wechseln Sie in JavaScript zu Beginn der Aufgabe von aria-busy
zu true
und am Ende zu false
.
Hinzugefügte ARIA-Attribute
Die implizite Rolle eines <progress>
-Elements ist progressbar
. Ich habe sie für Browser explizit angegeben, die diese implizite Rolle nicht haben. Ich habe außerdem das Attribut indeterminate
hinzugefügt, um dem Element explizit den Status „Unbekannt“ zuzuweisen. Das ist verständlicher als zu sehen, 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 für Screenreader wichtig, da der Nutzer bei Änderungen des Fortschritts mitgeteilt bekommt, wie weit der aktualisierte Fortschritt ist.
Stile
Das Progress-Element ist etwas komplizierter. Integrierte HTML-Elemente haben besondere versteckte 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ützliches, aber nicht erforderliches zusätzliches visuelles Signal sein kann.
<progress>
Layout
Die Breite des Fortschrittselements bleibt unverändert, sodass es sich entsprechend dem benötigten Platz im Design verkleinern und vergrößern kann. Die integrierten Stile werden entfernt, indem appearance
und border
auf none
gesetzt werden. So kann das Element plattformü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 1e3px
für _radius
verwendet die wissenschaftliche Schreibweise, um eine große Zahl auszudrücken, sodass border-radius
immer gerundet wird. Das entspricht 1000px
. Ich verwende diese Option gerne, da ich einen Wert verwenden möchte, der groß genug ist, damit ich ihn einmal festlegen und dann vergessen kann. Außerdem ist es einfacher, ihn bei Bedarf noch zu erhöhen: Ändern Sie einfach die 3 in eine 4, dann entspricht 1e4px
10000px
.1000px
overflow: hidden
ist ein umstrittener Stil. Dadurch wurde einiges vereinfacht, 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
erfolgen und bietet möglicherweise einige Möglichkeiten für Animationen oder bessere Abschlussstatus.
Vorgang abgeschlossen
CSS-Selektoren erledigen hier die harte Arbeit, indem sie den Maximalwert mit dem Wert vergleichen. Wenn sie übereinstimmen, ist der Fortschritt abgeschlossen. Wenn der Vorgang abgeschlossen ist, wird ein Pseudoelement generiert und an das Ende des Fortschrittselements angehängt. Dies dient als zusätzliche visuelle Aufforderung, dass der Vorgang abgeschlossen ist.
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 Browserstyles
Wenn Sie für Ihre Website ein dunkles und ein helles adaptives <progress>
-Element aktivieren möchten, ist nur color-scheme
erforderlich.
progress {
color-scheme: light dark;
}
Füllfarbe des Fortschritts einer einzelnen Unterkunft
Verwenden Sie accent-color
, um ein <progress>
-Element zu fä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: Sehr gut.
Vollständig benutzerdefinierte helle und dunkle Farben
Legen Sie zwei benutzerdefinierte Eigenschaften für das Element <progress>
fest, eine für die Titelfarbe und eine für die Farbe des Titelfortschritts. Gib in der Mediaabfrage prefers-color-scheme
neue Farbwerte für den Titel und den Titelfortschritt 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
Wir haben dem Element einen negativen Tabindex zugewiesen, damit es programmatisch fokussiert werden kann. Verwenden Sie :focus-visible
, um den Fokus anzupassen und den intelligenteren Fokusring zu aktivieren. In diesem Fall wird der Fokusring nicht durch einen Mausklick und Fokus gesetzt, sondern nur durch Tastaturklicks. Im YouTube-Video wird das Thema ausführlicher behandelt.
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 für jeden Browser sichtbar sind. Das Fortschrittselement besteht aus einem einzelnen Tag, das jedoch aus mehreren untergeordneten Elementen besteht, die über CSS-Pseudoselektoren freigelegt werden. Wenn Sie die Einstellung aktivieren, werden Ihnen in den Chrome-Entwicklertools die folgenden Elemente angezeigt:
- Klicken Sie mit der rechten Maustaste auf die Seite und wählen Sie Element untersuchen aus, um die Entwicklertools zu öffnen.
- Klicken Sie rechts oben im DevTools-Fenster auf das Zahnradsymbol für die Einstellungen.
- Klicken Sie unter der Überschrift Elemente auf das Kästchen User-Agent-Schatten-DOM anzeigen und aktivieren Sie es.
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 helle und dunkle Umgebungen 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 Pseudo-Selektor ::-moz-progress-bar
nur für das Element <progress>
angezeigt. Das bedeutet auch, dass wir den Titel nicht direkt einfärben können.
/* Firefox */
progress[value]::-moz-progress-bar {
background-color: var(--_progress);
}
Beachten Sie, dass in Firefox die Track-Farbe accent-color
und in iOS Safari eine hellblaue Spur verwendet wird. 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 Füllung des Tracks
Das Hinzufügen eines Übergangs zum inline-size
des Fortschrittselements funktioniert in Chromium, aber nicht in Safari. Firefox verwendet für seine ::-moz-progress-bar
auch keine Übergangseigenschaft.
/* 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 erstellen kann. Es wird ein Pseudo-Element für Chromium erstellt und ein Farbverlauf angewendet, der für alle drei Browser hin und her animiert wird.
Benutzerdefinierte Eigenschaften
Benutzerdefinierte Properties eignen sich für viele Zwecke. Eine meiner Lieblingsfunktionen ist es, einem ansonsten magisch aussehenden CSS-Wert einen Namen zu geben. Im Folgenden finden Sie eine ziemlich komplexe linear-gradient
, die aber einen schönen Namen hat. 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 Properties tragen auch dazu bei, dass der Code DRY bleibt, da wir diese browserspezifischen Auswahlkriterien nicht gruppieren können.
Die Keyframes
Ziel ist eine Endlosschleife, die hin und her geht. 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 jeder Browser erlaubt das Erstellen von Pseudoelementen im <progress>
-Element selbst oder die Animation der Fortschrittsanzeige. Mehr Browser unterstützen die Animation des Tracks als ein Pseudo-Element. Daher wechsele ich von Pseudo-Elementen als Basis zu animierten Balken.
Chromium-Pseudoelement
In Chromium ist das Pseudo-Element ::after
mit einer Position zulässig, 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);
}
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 Element <progress>
eine wichtige Rolle. Sie 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 bietet 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()
Hier erfolgt die UI/UX-Orchestrierung. Erstellen Sie zuerst eine setProgress()
-Funktion. Es sind keine Parameter erforderlich, da es 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 für das Attribut aria-busy
aktualisiert werden:
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
}
Attribute löschen, wenn die Lademenge 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 Wert für <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 Dezimalrechnungen in JavaScript beheben
Da ich mich für das Standardmaximum von 1 entschieden habe, werden in der Demo für die Inkrement- und Dekrementfunktionen Dezimalzahlen verwendet. JavaScript und andere Sprachen eignen sich dafür nicht immer gut.
Hier ist eine roundDecimals()
-Funktion, die den Überschuss des mathematischen Ergebnisses abschneidet:
const roundDecimals = (val, places) =>
+(Math.round(val + "e+" + places) + "e-" + places)
Runden Sie den Wert so, dass er präsentiert werden kann und gut 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 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
}
Den Fortschritt in den Mittelpunkt stellen
Nachdem die Werte aktualisiert wurden, sehen sehende Nutzer die Fortschrittsänderung, aber Nutzer von Screenreadern werden noch nicht über die Änderung informiert. Wenn Sie das Element <progress>
hervorheben, wird die Aktualisierung im Browser angekündigt.
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
Wie würden Sie es machen?
Wenn ich noch einmal die Chance bekommen würde, würde ich einige Dinge anders machen. Ich denke, dass die aktuelle Komponente optimiert werden kann und dass es möglich ist, eine Komponente ohne die Einschränkungen des Pseudoklassenstils des <progress>
-Elements zu erstellen. Es lohnt sich, sie auszuprobieren.
Lassen Sie uns unsere Herangehensweisen diversifizieren und alle Möglichkeiten kennenlernen, wie wir das Web entwickeln können.
Erstelle eine Demo, tweete mir Links und ich füge sie unten in den Abschnitt „Community-Remixe“ hinzu.