Créer un composant de fil d'Ariane

Présentation de base sur la création d'un composant de fil d'Ariane réactif et accessible pour que les utilisateurs puissent parcourir votre site.

Dans cet article, je souhaite partager notre réflexion sur la façon de créer des composants de fil d'Ariane. Tester la fonctionnalité

Démonstration

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

Présentation

Un composant fil d'Ariane indique où se trouve l'utilisateur dans la hiérarchie du site. Son nom vient de Hansel et Gretel, qui ont placé des fils d'Ariane derrière eux dans des bois sombres et ont réussi à retrouver leur chemin en traçant des miettes en arrière.

Les fils d'Ariane de cet article ne sont pas standards, ils ressemblent à des fils d'Ariane. Elles offrent des fonctionnalités supplémentaires en plaçant les pages sœurs directement dans la navigation à l'aide d'un <select>, ce qui permet un accès à plusieurs niveaux.

Expérience utilisateur en arrière-plan

Dans la vidéo de démonstration des composants ci-dessus, les catégories d'espace réservé sont des genres de jeux vidéo. Ce sentier est créé en parcourant le chemin suivant: home » rpg » indie » on sale, comme illustré ci-dessous.

Ce composant de fil d'Ariane doit permettre aux utilisateurs de se déplacer dans cette hiérarchie d'informations, en passant des branches et en sélectionnant des pages rapidement et avec précision.

Architecture de l’information

Je trouve utile de penser en termes de collections et d'éléments.

Collections

Une collection est un tableau d'options au choix. Sur la page d'accueil du prototype de fil d'Ariane de cet article, les collections sont : FPS, RPG, brawler, Donjon, Sport et Puzzle.

Éléments

Un jeu vidéo est un élément. Une collection spécifique peut également l'être si elle représente une autre collection. Par exemple, RPG correspond à un élément et à une collection valide. Lorsqu'il s'agit d'un article, l'utilisateur se trouve sur la page de cette collection. Par exemple, ils se trouvent sur la page "RPG", qui affiche une liste de jeux de rôle, y compris les sous-catégories supplémentaires AAA, Indé et Publié par l'utilisateur.

En informatique, ce composant de fil d'Ariane représente un tableau multidimensionnel:

const rawBreadcrumbData = {
  "FPS": {...},
  "RPG": {
    "AAA": {...},
    "indie": {
      "new": {...},
      "on sale": {...},
      "under 5": {...},
    },
    "self published": {...},
  },
  "brawler": {...},
  "dungeon crawler": {...},
  "sports": {...},
  "puzzle": {...},
}

Votre application ou votre site Web utilisera une architecture de l'information (AI) personnalisée créant un tableau multidimensionnel différent, mais j'espère que le concept de pages de destination de collection et de traversée de hiérarchie pourra également les intégrer à votre fil d'Ariane.

Mises en page

Markup

Un bon composant commence par un code HTML approprié. Dans cette section suivante, je couvrirai mes choix de balisage et leur impact sur le composant global.

Jeu sombre et clair

<meta name="color-scheme" content="dark light">

La balise Meta color-scheme dans l'extrait ci-dessus informe le navigateur que cette page souhaite utiliser les styles clair et sombre. L'exemple de fil d'Ariane n'inclut aucun CSS pour ces jeux de couleurs. Il utilise donc les couleurs par défaut fournies par le navigateur.

<nav class="breadcrumbs" role="navigation"></nav>

Il est approprié d'utiliser l'élément <nav> pour la navigation sur le site, qui possède un rôle de navigation ARIA implicite. Lors des tests, j'ai remarqué que l'attribut role modifiait la façon dont un lecteur d'écran interagissait avec l'élément. Il était en fait annoncé en tant que navigation. J'ai donc choisi de l'ajouter.

Icônes

Lorsqu'une icône est répétée sur une page, l'élément SVG <use> signifie que vous pouvez définir l'élément path une seule fois et l'utiliser pour toutes les instances de l'icône. Cela évite que les mêmes informations de chemin d'accès soient répétées, ce qui entraînerait des documents plus volumineux et le risque d'incohérences dans les chemins d'accès.

Pour utiliser cette technique, ajoutez un élément SVG masqué à la page et encapsulez les icônes dans un élément <symbol> avec un ID unique:

<svg style="display: none;">

  <symbol id="icon-home">
    <title>A home icon</title>
    <path d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
  </symbol>

  <symbol id="icon-dropdown-arrow">
    <title>A down arrow</title>
    <path d="M19 9l-7 7-7-7"/>
  </symbol>

</svg>

Le navigateur lit le code HTML SVG, place les informations de l'icône en mémoire et poursuit le reste de la page en référençant l'ID pour d'autres utilisations de l'icône, comme ceci:

<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
  <use href="#icon-home" />
</svg>

<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
  <use href="#icon-dropdown-arrow" />
</svg>

Outils de développement affichant un élément SVG affiché

Définissez-la une seule fois et utilisez-la autant de fois que vous le souhaitez, avec un impact minimal sur les performances de la page et un style flexible. Notez que aria-hidden="true" a été ajouté à l'élément SVG. Les icônes ne sont pas utiles pour les personnes naviguant qui n'entend que le contenu. Si vous les masquez, elles n'ajoutent pas de bruit inutile à ces utilisateurs.

C'est là que le fil d'Ariane traditionnel et ceux de ce composant divergent. Normalement, il ne s'agirait que d'un lien <a>. Toutefois, j'ai ajouté une expérience utilisateur de balayage avec une sélection masquée. La classe .crumb est responsable de la mise en page du lien et de l'icône, tandis que la classe .crumbicon est responsable de l'empilement de l'icône et de la sélection de l'élément. Je l'ai appelé un lien fractionné, car ses fonctions sont très semblables à celles de split-button, mais pour la navigation sur les pages.

<span class="crumb">
  <a href="#sub-collection-b">Category B</a>
  <span class="crumbicon">
    <svg>...</svg>
    <select class="disguised-select" title="Navigate to another category">
      <option>Category A</option>
      <option selected>Category B</option>
      <option>Category C</option>
    </select>
  </span>
</span>

Un lien et certaines options n'ont rien de spécial, mais ajoutent des fonctionnalités à un fil d'Ariane simple. L'ajout d'un title à l'élément <select> est utile pour les utilisateurs de lecteurs d'écran, car il leur fournit des informations sur l'action du bouton. Cependant, il fournit la même aide à tous les autres utilisateurs. Vous le verrez au premier plan sur l'iPad. Un attribut fournit des informations contextuelles sur les boutons à de nombreux utilisateurs.

Capture d&#39;écran avec le passage de la souris sur l&#39;élément de sélection invisible et l&#39;affichage de son info-bulle contextuelle.

Décorations du séparateur

<span class="crumb-separator" aria-hidden="true">→</span>

Les séparateurs sont facultatifs. N'en ajouter qu'un est également très bien (voir le troisième exemple dans la vidéo ci-dessus). J'attribue ensuite une valeur aria-hidden="true" à chaque élément, car il s'agit d'éléments décoratifs et non d'une information qu'un lecteur d'écran doit annoncer.

La propriété gap, décrite ci-dessous, permet de les espacer facilement.

Styles

Étant donné que la couleur utilise les couleurs système, il s'agit principalement de blancs et de piles de styles.

Orientation et flux de la mise en page

Outils de développement affichant l&#39;alignement de la barre de navigation du fil d&#39;Ariane avec sa fonctionnalité de superposition Flexbox

L'élément de navigation principal nav.breadcrumbs définit une propriété personnalisée de champ d'application que les enfants peuvent utiliser et établit une mise en page horizontale alignée verticalement. Cela garantit que les miettes, les séparateurs et les icônes sont alignées.

.breadcrumbs {
  --nav-gap: 2ch;

  display: flex;
  align-items: center;
  gap: var(--nav-gap);
  padding: calc(var(--nav-gap) / 2);
}

Un fil d&#39;Ariane aligné verticalement sur les superpositions Flexbox.

Chaque .crumb établit également une mise en page horizontale alignée verticalement avec un écart, mais cible spécifiquement ses enfants de lien et spécifie le style white-space: nowrap. Ce point est essentiel pour les fils d'Ariane composés de plusieurs mots, car nous ne souhaitons pas qu'ils s'affichent sur plusieurs lignes. Plus loin dans cet article, nous ajouterons des styles pour gérer le dépassement horizontal causé par cette propriété white-space.

.crumb {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--nav-gap) / 4);

  & > a {
    white-space: nowrap;

    &[aria-current="page"] {
      font-weight: bold;
    }
  }
}

Ajout de aria-current="page" pour permettre au lien actuel de la page de se démarquer des autres. Non seulement les utilisateurs de lecteurs d'écran verront un indicateur clair que le lien est vers la page actuelle, mais nous avons également appliqué un style visuel à l'élément pour aider les utilisateurs voyants à obtenir une expérience similaire.

Le composant .crumbicon utilise une grille pour empiler une icône SVG avec un élément <select> "presque invisible".

Outils de développement pour les grilles affichés en superposition d&#39;un bouton où la ligne et la colonne sont toutes deux nommées &quot;pile&quot;.

.crumbicon {
  --crumbicon-size: 3ch;

  display: grid;
  grid: [stack] var(--crumbicon-size) / [stack] var(--crumbicon-size);
  place-items: center;

  & > * {
    grid-area: stack;
  }
}

L'élément <select> se trouve en dernier dans le DOM. Il se trouve donc au-dessus de la pile et est interactif. Ajoutez un style opacity: .01 pour que l'élément reste utilisable. Vous obtenez alors une zone de sélection qui s'adapte parfaitement à la forme de l'icône. C'est un bon moyen de personnaliser l'apparence d'un élément <select> tout en conservant les fonctionnalités intégrées.

.disguised-select {
  inline-size: 100%;
  block-size: 100%;
  opacity: .01;
  font-size: min(100%, 16px); /* Defaults to 16px; fixes iOS zoom */
}

Dépassement

Les fils d'Ariane doivent pouvoir représenter un très long chemin. J'aime laisser les éléments hors écran horizontalement, lorsque c'est approprié, et j'ai senti que ce composant de fil d'Ariane était bien qualifié.

.breadcrumbs {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-padding-inline: calc(var(--nav-gap) / 2);

  & > .crumb:last-of-type {
    scroll-snap-align: end;
  }

  @supports (-webkit-hyphens:none) { & {
    scroll-snap-type: none;
  }}
}

Les styles de dépassement permettent de configurer l'expérience utilisateur suivante:

  • Défilement horizontal avec confinement pour le défilement hors limites.
  • Marge intérieure du défilement horizontal.
  • Un seul point d'ancrage dans la dernière miette. Cela signifie qu'au chargement de la page, le premier chargement de fil d'actualités est ancré et visible.
  • Suppression du point d'ancrage de Safari, qui rencontre des difficultés avec les combinaisons de défilement horizontal et d'effet d'ancrage.

Requêtes média

Pour les fenêtres d'affichage de petite taille, un ajustement subtil est de masquer le libellé "Accueil" en ne laissant que l'icône:

@media (width <= 480px) {
  .breadcrumbs .home-label {
    display: none;
  }
}

À côté du fil d&#39;Ariane avec et sans libellé de domicile, à des fins de comparaison.

Accessibilité

Mouvement

Il n'y a pas beaucoup de mouvement dans ce composant, mais en encapsulant la transition dans une vérification prefers-reduced-motion, nous pouvons empêcher les mouvements indésirables.

@media (prefers-reduced-motion: no-preference) {
  .crumbicon {
    transition: box-shadow .2s ease;
  }
}

Aucun autre style n'a besoin de changer. Les effets de pointage et de mise au point sont excellents et pertinents sans transition, mais si le mouvement est correct, une transition subtile est ajoutée à l'interaction.

JavaScript

Tout d'abord, quel que soit le type de routeur que vous utilisez sur votre site ou dans votre application, lorsqu'un utilisateur modifie le fil d'Ariane, l'URL doit être mise à jour et l'utilisateur doit afficher la page appropriée. Ensuite, pour normaliser l'expérience utilisateur, assurez-vous qu'aucune navigation inattendue ne se produit lorsque les utilisateurs ne font que parcourir les options <select>.

Deux mesures critiques concernant l'expérience utilisateur doivent être gérées par JavaScript: select a changé et empêche le déclenchement d'événements de modification <select>.

La prévention des événements "eager" est nécessaire en raison de l'utilisation d'un élément <select>. Sous Windows Edge, et probablement d'autres navigateurs, l'événement de sélection changed se déclenche lorsque l'utilisateur parcourt les options à l'aide du clavier. C'est pourquoi j'ai appelé "eager", car l'utilisateur n'a choisi qu'un pseudo-sélectionné, comme un survol ou un focus, mais n'a pas confirmé le choix avec enter ou click. L'événement eager rend cette fonctionnalité de modification de catégorie de composants inaccessible, car l'ouverture de la zone de sélection et la simple navigation sur un élément déclencheront l'événement et modifieront la page, avant que l'utilisateur ne soit prêt.

Événement modifié <select> meilleur

const crumbs = document.querySelectorAll('.breadcrumbs select')
const allowedKeys = new Set(['Tab', 'Enter', ' '])
const preventedKeys = new Set(['ArrowUp', 'ArrowDown'])

// watch crumbs for changes,
// ensures it's a full value change, not a user exploring options via keyboard
crumbs.forEach(nav => {
  let ignoreChange = false

  nav.addEventListener('change', e => {
    if (ignoreChange) return
    // it's actually changed!
  })

  nav.addEventListener('keydown', ({ key }) => {
    if (preventedKeys.has(key))
      ignoreChange = true
    else if (allowedKeys.has(key))
      ignoreChange = false
  })
})

Pour ce faire, la stratégie consiste à surveiller les événements liés au clavier pour chaque élément <select> et à déterminer si la touche appuyée était une confirmation de navigation (Tab ou Enter) ou de la navigation spatiale (ArrowUp ou ArrowDown). Avec cette détermination, le composant peut décider d'attendre ou de s'arrêter lorsque l'événement pour l'élément <select> se déclenche.

Conclusion

Maintenant que vous savez comment je l'ai fait, comment le feriez-vous‽ 😃 ?

Diversissons nos approches et apprenons toutes les façons de créer sur le Web. Créez une démo, cliquez sur les liens tweet me, et je l'ajouterai à la section "Remix" de la communauté ci-dessous.

Remix de la communauté