Créer un composant de barre de chargement

Présentation générale de la création d'une barre de chargement accessible et adaptable aux couleurs avec l'élément <progress>.

Dans ce post, je vais vous expliquer comment créer accessible via l'élément <progress>. Essayez le une démonstration et regardez la .

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph> Le thème clair et sombre, l'indéterminé, l'augmentation et l'achèvement sont présentés sur Chrome.

Si vous préférez la vidéo, voici une version YouTube de cet article:

Présentation

La <progress> fournit aux utilisateurs un retour visuel et audio sur l'achèvement. Ce un retour visuel est utile pour des scénarios tels que la progression dans un formulaire, afficher des informations de téléchargement ou d'importation, ou même indiquer la quantité de progression est inconnue, mais la tâche est toujours active.

Ce défi de l'IUG a collaboré avec l'élément HTML <progress> existant pour simplifier l'accessibilité. La les couleurs et les mises en page repoussent les limites de la personnalisation de l'élément intégré, pour de moderniser le composant et de le faire mieux s'intégrer dans les systèmes de conception.

<ph type="x-smartling-placeholder">
</ph> Les onglets clairs et sombres de chaque navigateur offrent 
    Présentation de l&#39;icône adaptative de haut en bas: 
    Safari, Firefox, Chrome. <ph type="x-smartling-placeholder">
</ph> Démonstration dans Firefox, Safari, iOS Safari, Chrome et Chrome pour Android, avec des couleurs claires et sombres.

Majoration

J'ai choisi d'encapsuler l'élément <progress> dans une <label> ainsi Je pourrais ignorer les attributs de relation explicites au profit d'un modèle implicite relationnel. J'ai également étiqueté un élément parent affecté par l'état de chargement, donc l'écran les technologies de lecture peuvent transmettre ces informations à un utilisateur.

<progress></progress>

Si aucun value ne s'affiche, la progression de l'élément est indéterminée. La valeur par défaut de l'attribut max est 1. La progression est donc comprise entre 0 et 1. Définir max sur 100, par exemple, définit la plage sur 0-100. J'ai choisi de rester dans la et 1, ce qui traduit les valeurs de progression à 0,5 ou 50%.

Progression de l'encapsulation par étiquette

Dans une relation implicite, un élément de progression est encapsulé par un libellé semblable à celui-ci:

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

Dans ma démonstration, j'ai choisi d'inclure le libellé correspondant aux lecteurs d'écran uniquement. Pour ce faire, encapsulez le texte du libellé dans une <span> et appliquez des styles. pour qu'elle soit en dehors de l'écran:

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

Avec le code CSS suivant de WebAIM associé:

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

Capture d&#39;écran des outils de développement révélant l&#39;élément &quot;Screen ready only&quot; (prêt à l&#39;écran uniquement).

Zone affectée par la progression du chargement

Si votre vision est saine, vous pouvez facilement associer un indicateur de progression avec des éléments et des zones de page connexes, mais pour les utilisateurs malvoyants, si clair. Pour améliorer cela, attribuez aria-busy à l'élément de niveau supérieur qui changera une fois le chargement terminé. De plus, indiquer une relation entre la progression et la zone de chargement par aria-describedby

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

À partir de JavaScript, basculez aria-busy sur true au début de la tâche et sur false.

Ajouts d'attributs Aria

Alors que le rôle implicite d'un élément <progress> est progressbar, le contenu est explicite pour les navigateurs qui n'ont pas ce rôle implicite. J'ai également ajouté l'attribut indeterminate pour placer explicitement l'élément dans un état inconnu, à savoir plus claire que d'observer l'élément sans value défini.

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

Utilisez tabindex="-1" pour que l'élément de progression puisse être sélectionné à partir de JavaScript. Ceci est important pour la technologie de lecteur d'écran, car en donnant l'accent sur la progression au fur et à mesure que la progression évolue, annoncera à l'utilisateur l'état d'avancement de la mise à jour.

Styles

L'élément de progression est un peu complexe au niveau du style. HTML intégré les éléments comportent des parties cachées spéciales qui peuvent être difficiles à sélectionner et souvent ne proposent qu'un ensemble limité de propriétés à définir.

Mise en page

Les styles de mise en page sont conçus pour permettre une certaine flexibilité dans la progression la taille et la position du libellé de l'élément. Un état d'achèvement spécial est ajouté qui peut être un indice visuel supplémentaire utile, mais non obligatoire.

Mise en page <progress>

La largeur de l'élément de progression reste inchangée afin qu'elle puisse être réduite et agrandie. avec l'espace nécessaire dans la conception. Les styles intégrés sont supprimés en définissant appearance et border sur none. Cela est fait pour que l'élément puisse être sont normalisés entre les navigateurs, car chacun a son propre style pour ses .

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;
}

La valeur de 1e3px pour _radius utilise un numéro scientifique pour exprimer un un grand nombre de sorte que border-radius soit toujours arrondi. Cela équivaut à 1000px J'aime l'utiliser, car mon objectif est d'utiliser une valeur suffisamment élevée pour Je peux le définir et l'oublier (et son écriture est plus court que 1000px). Il est également Il est facile de les agrandir si nécessaire: remplacez simplement 3 par 4, et 1e4px sera équivalente à 10000px.

overflow: hidden est utilisé et constitue un style litigieux. Quelques les choses. Par exemple, il n'est pas nécessaire de transmettre les valeurs border-radius à la suivre et suivre les éléments de remplissage ; mais il fallait également qu'il n'y ait aucun enfant du progrès pourraient vivre en dehors de l'élément. Une autre itération de cette progression personnalisée l'élément peut être effectué sans overflow: hidden et risque d'ouvrir des animations ou de meilleurs états de fin.

Processus terminé

Dans ce cas, les sélecteurs CSS sont les plus complexes en comparant le maximum à la valeur. Si ces valeurs correspondent, la progression est terminée. Une fois l'opération terminée, un pseudo-élément est généré et ajouté à la fin de l'élément de progression, ce qui constitue un indicateur visuel supplémentaire de la fin de l'opération.

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);
}

Capture d&#39;écran de la barre de chargement à 100% et montrant une coche à la fin

Couleur

Le navigateur applique ses propres couleurs pour l'élément de progression et s'adapte à clair et sombre à l'aide d'une seule propriété CSS. Vous pouvez vous appuyer sur certaines des sélecteurs spéciaux propres au navigateur.

Styles de navigateur clair et sombre

Pour activer l'élément <progress> adaptatif sombre et clair sur votre site, procédez comme suit : color-scheme est tout ce qu'il vous faut.

progress {
  color-scheme: light dark;
}

Couleur remplie de la progression d'une propriété unique

Pour colorer un élément <progress>, utilisez accent-color.

progress {
  accent-color: rebeccapurple;
}

Notez que la couleur d'arrière-plan de la piste passe du clair au foncé accent-color Le navigateur assure le contraste, ce qui est plutôt net.

Couleurs claires et sombres entièrement personnalisées

Définissez deux propriétés personnalisées sur l'élément <progress>, l'une pour la couleur de la piste. et l'autre pour la couleur du suivi de la progression. Au cœur de la prefers-color-scheme requête média, fournissez de nouvelles valeurs de couleur pour le titre et suivez la progression.

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%);
  }
}

Styles de focus

Nous avons précédemment attribué à l'élément un index d'onglet négatif afin qu'il puisse être programmatique. concentré. Utilisez :focus-visible jusqu'à Personnalisez la mise au point pour activer le style de l'anneau de mise au point plus intelligent. Avec cela, une souris les clics et le focus n'affichent pas l'anneau de focus, contrairement aux clics du clavier. La Vidéo YouTube approfondit ce sujet. mérite d'être examiné.

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

Capture d&#39;écran de la barre de chargement entourée d&#39;un anneau de focus. Toutes les couleurs correspondent.

Styles personnalisés dans les différents navigateurs

Personnalisez les styles en sélectionnant les parties d'un élément <progress> qui correspondent chacune le navigateur expose. L'élément Progress utilisé est une balise unique, mais elle est constituée d'un quelques éléments enfants exposés via des sélecteurs de pseudo CSS. Outils pour les développeurs Chrome affiche les éléments suivants si vous activez ce paramètre:

  1. Effectuez un clic droit sur votre page, puis sélectionnez Inspect Element (Inspecter l'élément) pour afficher les outils de développement.
  2. Cliquez sur l'icône en forme de roue dentée des paramètres en haut à droite de la fenêtre "DevTools".
  3. Sous l'en-tête Elements, recherchez et activez l'option Afficher l'ombre user-agent DOM.

Capture d&#39;écran montrant l&#39;emplacement dans les outils de développement pour permettre l&#39;exposition du Shadow DOM du user-agent.

Styles Safari et Chromium

Les navigateurs basés sur WebKit tels que Safari et Chromium exposent ::-webkit-progress-bar et ::-webkit-progress-value, qui permettent à un sous-ensemble CSS à utiliser. Pour l'instant, définissez background-color à l'aide des propriétés personnalisées créées précédemment, qui s'adaptent à la lumière et à l'obscurité.

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

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

Capture d&#39;écran montrant les éléments internes de l&#39;élément de progression.

Styles Firefox

Firefox n'expose que le pseudo-sélecteur ::-moz-progress-bar au niveau du Élément <progress>. Cela signifie également que nous ne pouvons pas teindre directement la piste.

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

Capture d&#39;écran de Firefox et où se trouvent les éléments de progression.

Capture d&#39;écran de l&#39;angle de débogage où Safari, iOS Safari, 
  Dans Firefox, Chrome et Chrome sur Android, la barre de chargement est affichée.

Notez que la couleur de suivi est définie sur accent-color pour Firefox et Safari pour iOS. présente une ligne bleu clair. Il en va de même en mode sombre: Firefox a une piste sombre, mais pas la couleur personnalisée que nous avons définie, et cela fonctionne dans les navigateurs basés sur WebKit.

Animation

Lorsque vous travaillez avec des pseudo-sélecteurs intégrés au navigateur, d'un ensemble de propriétés CSS autorisées.

Animer la piste qui se remplit

Ajouter une transition au inline-size sur l'élément de progression fonctionne dans Chromium, mais pas dans Safari. Firefox fait aussi n'utilisez pas de propriété de transition sur ::-moz-progress-bar.

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

Animer l'état :indeterminate

Ici, je suis un peu plus créatif et je peux proposer une animation. Un pseudo-élément pour Chromium est créé, et un dégradé est appliqué, qui est animé en arrière et pour les trois navigateurs.

Propriétés personnalisées

Les propriétés personnalisées conviennent à de nombreux besoins, mais l'une de mes préférées pour donner un nom à une valeur CSS tout à fait magique. Le contenu suivant est une complexe linear-gradient, mais avec un joli nom. Son objectif et ses cas d'utilisation doivent être clairement compris.

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;
}

Les propriétés personnalisées permettent également au code de rester DRY, car il est impossible vous pouvez regrouper ces sélecteurs spécifiques au navigateur.

Les images clés

L'objectif est une animation infinie qui va dans les deux sens. Le début et la fin les images clés sont définies dans CSS. Une seule image clé est nécessaire : celle du milieu à 50%, pour créer une animation qui revient à son point de départ, encore une fois !

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

Cibler chaque navigateur

Tous les navigateurs n'autorisent pas la création de pseudo-éléments sur le <progress> lui-même ou autorise l'animation de la barre de progression. Compatibilité avec d'autres navigateurs pour animer la piste plutôt qu'un pseudo-élément. une base et en barres d’animation.

Pseudo-élément Chromium

Chromium autorise le pseudo-élément: ::after, utilisé avec une position de couverture. de l'élément. Les propriétés personnalisées indéterminées sont utilisées, et le retour et l'animation suivante fonctionne très bien.

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);
}
Barre de progression Safari

Dans Safari, les propriétés personnalisées et une animation sont appliquées au Barre de progression pseudo-élément:

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

Pour Firefox, les propriétés personnalisées et une animation sont également appliquées à la Barre de progression pseudo-élément:

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 joue un rôle important avec l'élément <progress>. Contrôle la valeur envoyée à l'élément et s'assure que le champ pour les lecteurs d'écran.

const state = {
  val: null
}

La démo propose des boutons permettant de contrôler la progression. ils mettent à jour state.val puis d'appeler une fonction pour mettre à jour DOM

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

setProgress()

C'est dans cette fonction que s'effectue l'orchestration de l'UI/de l'expérience utilisateur. Commencez par créer un fonction setProgress(). Aucun paramètre n'est requis, car l'instance a accès au Objet state, élément de progression et zone <main>.

const setProgress = () => {
  
}

Définir l'état de chargement sur la zone <main>

Selon que la progression est terminée ou non, le <main> associé doit être mis à jour aria-busy attribut:

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

Effacer les attributs si la quantité de chargement est inconnue

Si la valeur est inconnue ou n'est pas définie, null dans cette utilisation, supprimez value et aria-valuenow. Cela transformera <progress> en indéterminé.

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

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

Résoudre les problèmes de mathématiques décimales JavaScript

Comme j'ai choisi de conserver la progression par défaut (1), les fonctions d'incrémentation et de décrémentation utilisent des calculs décimaux. JavaScript et autres langues, ne sont pas toujours doués pour de sécurité. Voici une fonction roundDecimals() qui supprime l'excès de la formule mathématique résultat:

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

Arrondissez la valeur de façon à ce qu'elle soit présentée et lisible:

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 + "%"
}

Définir la valeur pour les lecteurs d'écran et l'état du navigateur

Cette valeur est utilisée à trois emplacements dans le DOM:

  1. L'attribut value de l'élément <progress>.
  2. L'attribut aria-valuenow
  3. Le contenu textuel interne de <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
}

Cibler la progression

Une fois les valeurs mises à jour, les utilisateurs voyants verront la progression changer, mais l’écran les utilisateurs de lecteurs ne sont pas encore informés de ce changement. Sélectionnez <progress> et le navigateur annoncera la mise à jour.

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()
}

Capture d&#39;écran de l&#39;application VoiceOver sur Mac OS 
  de lire la progression de la barre
de chargement à l&#39;utilisateur.

Conclusion

Maintenant que vous savez comment j'ai fait, comment feriez-vous ? 😃

Il y a certainement quelques modifications que j'aimerais apporter si je me permets de vous laisser le temps. Je pense qu'il est possible de nettoyer le composant actuel et d'en créer un sans les limites de style de pseudo-classe de l'élément <progress>. Cela vaut la peine d'être exploré !

Diversifiez nos approches et découvrons toutes les manières de créer des applications sur le Web.

Créer une démonstration, me envoyer des tweets et je l'ajouterai à la section des remix de la communauté ci-dessous.

Remix de la communauté