Créer un composant de navigation latérale

Présentation des principes de base de la création d'un menu de navigation latéral responsif

Dans cet article, je souhaite partager avec vous comment j'ai prototypé un composant Sidenav pour le Web qui est responsif, avec état, qui prend en charge la navigation au clavier, qui fonctionne avec et sans JavaScript, et qui fonctionne sur plusieurs navigateurs. Essayez la démonstration.

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

Présentation

Il est difficile de créer un système de navigation réactif. Certains utilisateurs se serviront d'un clavier, d'autres d'un ordinateur de bureau puissant, d'autres d'un petit appareil mobile. Tous les visiteurs doivent pouvoir ouvrir et fermer le menu.

Démonstration de la mise en page responsive sur ordinateur vers mobile
Thème clair et sombre sur iOS et Android

Tactique Web

Lors de cette exploration des composants, j'ai eu le plaisir de combiner quelques fonctionnalités essentielles de la plate-forme Web:

  1. CSS :target
  2. Grille CSS
  3. transforms CSS
  4. Requêtes multimédias CSS pour la fenêtre d'affichage et les préférences de l'utilisateur
  5. JS pour focus améliorations de l'expérience utilisateur

Ma solution comporte une barre latérale et ne s'affiche que dans la fenêtre d'affichage "mobile" de 540px ou moins. 540px sera notre point d'arrêt pour basculer entre la mise en page interactive sur mobile et la mise en page statique pour ordinateur.

Pseudo-classe CSS :target

Un lien <a> définit le hachage d'URL sur #sidenav-open et l'autre sur vide (''). Enfin, un élément possède la valeur id correspondant au hachage:

<a href="#sidenav-open" id="sidenav-button" title="Open Menu" aria-label="Open Menu">

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>

<aside id="sidenav-open">
  …
</aside>

En cliquant sur chacun de ces liens, je modifie l'état de hachage de l'URL de notre page, puis j'affiche et masque la barre de navigation latérale avec une pseudo-classe:

@media (max-width: 540px) {
  #sidenav-open {
    visibility: hidden;
  }

  #sidenav-open:target {
    visibility: visible;
  }
}

Grille CSS

Auparavant, je n'utilisais que les mises en page et les composants de navigation latérale à position absolue ou fixe. Cependant, la grille, avec sa syntaxe grid-area, nous permet d'attribuer plusieurs éléments à la même ligne ou colonne.

Piles

L'élément de mise en page principal #sidenav-container est une grille qui crée une ligne et deux colonnes, chacune étant nommée stack. Lorsque l'espace est limité, le CSS attribue tous les enfants de l'élément <main> au même nom de grille, en plaçant tous les éléments dans le même espace, ce qui crée une pile.

#sidenav-container {
  display: grid;
  grid: [stack] 1fr / min-content [stack] 1fr;
  min-height: 100vh;
}

@media (max-width: 540px) {
  #sidenav-container > * {
    grid-area: stack;
  }
}

<aside> est l'élément d'animation qui contient la navigation latérale. Elle a deux enfants: le conteneur de navigation <nav> nommé [nav] et un fond <a> nommé [escape], qui permet de fermer le menu.

#sidenav-open {
  display: grid;
  grid-template-columns: [nav] 2fr [escape] 1fr;
}

Ajustez 2fr et 1fr pour trouver le format qui vous convient pour la superposition du menu et le bouton de fermeture de l'espace négatif.

Démonstration de ce qui se passe lorsque vous modifiez le ratio.

Transformations et transitions CSS 3D

Notre mise en page est désormais empilée à la taille de la fenêtre d'affichage mobile. Jusqu'à ce que j'ajoute de nouveaux styles, il se superpose par défaut à notre article. Voici quelques exemples d’expérience utilisateur que je recherche dans la section suivante:

  • Animer l'ouverture et la fermeture
  • L'animation ne doit être déclenchée par un mouvement que si l'utilisateur est d'accord.
  • Animer visibility pour que la sélection au clavier ne passe pas dans l'élément hors écran

Lorsque je commence à implémenter des animations de mouvement, je veux commencer par l'accessibilité au premier plan.

Mouvement accessible

Tout le monde ne voudra pas d'une expérience de mouvement coulissant. Dans notre solution, cette préférence est appliquée en ajustant une variable CSS --duration dans une requête média. Cette valeur de requête média représente les préférences de mouvement d'un utilisateur pour le système d'exploitation (si disponible).

#sidenav-open {
  --duration: .6s;
}

@media (prefers-reduced-motion: reduce) {
  #sidenav-open {
    --duration: 1ms;
  }
}
Démonstration de l'interaction avec et sans durée appliquée.

Désormais, lorsque notre barre de navigation latérale glisse ouverte et fermée, si un utilisateur préfère un mouvement réduit, je déplace instantanément l'élément en vue, en conservant l'état sans mouvement.

Transition, transformation et traduction

Sortie latérale de navigation (par défaut)

Pour définir l'état par défaut de notre navigation latérale sur mobile sur un état hors écran, je positionne l'élément avec transform: translateX(-110vw).

Notez que j'ai ajouté un autre 10vw au code hors écran classique de -100vw, pour que l'élément box-shadow de la barre de navigation latérale ne s'affiche pas dans la fenêtre d'affichage principale lorsqu'il est masqué.

@media (max-width: 540px) {
  #sidenav-open {
    visibility: hidden;
    transform: translateX(-110vw);
    will-change: transform;
    transition:
      transform var(--duration) var(--easeOutExpo),
      visibility 0s linear var(--duration);
  }
}
Navigation latérale dans

Lorsque l'élément #sidenav correspond à :target, définissez la position de translateX() sur la position d'accueil 0, puis observez que CSS fait glisser l'élément de sa position extérieure (-110vw) vers sa position d'entrée de 0 sur var(--duration) lorsque le hachage de l'URL est modifié.

@media (max-width: 540px) {
  #sidenav-open:target {
    visibility: visible;
    transform: translateX(0);
    transition:
      transform var(--duration) var(--easeOutExpo);
  }
}

Visibilité de la transition

L'objectif est maintenant de masquer le menu aux lecteurs d'écran lorsqu'il est fermé afin que les systèmes ne le mettent pas au premier plan. Pour ce faire, je définis une transition de visibilité lorsque :target change.

  • Lors de l'ouverture, ne changez pas la visibilité ; soyez visible immédiatement pour que l'élément glisse et que le curseur soit placé dans le cadre.
  • Lors de la sortie, la visibilité de la transition est différée, afin qu'elle bascule sur hidden à la fin de la transition.

Amélioration de l'expérience utilisateur pour l'accessibilité

Cette solution repose sur la modification de l'URL pour que l'état soit géré. Naturellement, l'élément <a> doit être utilisé ici, et il obtient sans frais de bonnes fonctionnalités d'accessibilité. Ajoutons à nos éléments interactifs des étiquettes qui expliquent clairement l'intention.

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>

<a href="#sidenav-open" id="sidenav-button" class="hamburger" title="Open Menu" aria-label="Open Menu">
  <svg>...</svg>
</a>
Démonstration de l'expérience utilisateur avec voix off et interaction au clavier.

Nos boutons d'interaction principaux indiquent clairement leur intention pour la souris et le clavier.

:is(:hover, :focus)

Ce pseudo-sélecteur fonctionnel CSS pratique nous permet d'inclure rapidement nos styles de pointage en les partageant avec le curseur.

.hamburger:is(:hover, :focus) svg > line {
  stroke: hsl(var(--brandHSL));
}

Sprinkle sur JavaScript

Appuyez sur escape pour fermer

La touche Escape de votre clavier devrait fermer le menu à droite ? Branchons ça.

const sidenav = document.querySelector('#sidenav-open');

sidenav.addEventListener('keyup', event => {
  if (event.code === 'Escape') document.location.hash = '';
});
Historique du navigateur

Pour éviter que les interactions d'ouverture et de fermeture n'empilent plusieurs entrées dans l'historique du navigateur, ajoutez le code JavaScript suivant intégré au bouton de fermeture:

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu" onchange="history.go(-1)"></a>

Cette action supprime l'entrée de l'historique des URL à la fermeture, ce qui fait comme si le menu n'avait jamais été ouvert.

Concentration de l'expérience utilisateur

L'extrait suivant nous aide à mettre en évidence les boutons d'ouverture et de fermeture après leur ouverture ou leur fermeture. Je veux qu'il soit facile d'activer/de désactiver.

sidenav.addEventListener('transitionend', e => {
  const isOpen = document.location.hash === '#sidenav-open';

  isOpen
      ? document.querySelector('#sidenav-close').focus()
      : document.querySelector('#sidenav-button').focus();
})

Lorsque la barre de navigation latérale s'ouvre, sélectionnez le bouton de fermeture. Lorsque la barre de navigation latérale se ferme, ciblez le bouton d'ouverture. Pour ce faire, j'appelle focus() sur l'élément dans JavaScript.

Conclusion

Maintenant que tu sais comment j'ai fait, comment tu en ferais ?! Cela donne une architecture de composants amusante ! Qui créera la 1re version avec des emplacements ? 🙂

Diversifions nos approches et apprenons toutes les façons de créer sur le Web. Créez un Glitch, puis envoyez-moi votre version par tweet et je l'ajouterai à la section Remix de la communauté ci-dessous.

Remix de la communauté