Créer un composant de navigation latérale

Présentation de base sur la création d'un panneau latéral coulissant responsif

Dans cet article, je vais vous expliquer comment j'ai prototypé un composant Sidenav pour le Web qui est responsif, avec état, compatible avec la navigation au clavier, fonctionne avec et sans JavaScript, et fonctionne dans tous les navigateurs. Essayez la démo.

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

Présentation

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

Démo de mise en page responsive pour ordinateur et mobile
Thème clair et sombre désactivés sur iOS et Android

Stratégies Web

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

  1. CSS :target
  2. Grille CSS
  3. Transformations CSS
  4. Requêtes média 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 seule barre latérale et ne s'active que lorsque la fenêtre d'affichage "mobile" est de 540px ou moins. 540px sera notre point d'inflexion pour basculer entre la mise en page interactive pour 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 le 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, l'état du hachage de l'URL de notre page change. Ensuite, à l'aide d'une pseudo-classe, j'affiche et masque le panneau latéral:

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

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

Grille CSS

Auparavant, je n'utilisais que des mises en page et des composants de barre latérale en 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 portant le nom stack. Lorsque l'espace est limité, le CSS attribue le même nom de grille à tous les enfants de l'élément <main>, ce qui place tous les éléments dans le même espace et 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. Il comporte deux enfants: le conteneur de navigation <nav> nommé [nav] et un arrière-plan <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 ratio qui vous convient pour le menu superposé et son bouton de fermeture dans l'espace négatif.

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

Transformations et transitions CSS 3D

Notre mise en page est maintenant empilée pour une taille de fenêtre d'affichage mobile. Tant que je n'ajoute pas de nouveaux styles, il se superpose à notre article par défaut. Voici quelques-uns des éléments d'expérience utilisateur que je recherche dans cette section:

  • Animer l'ouverture et la fermeture
  • N'animer avec du mouvement que si l'utilisateur l'accepte
  • Animer visibility pour que le focus du clavier n'entre pas dans l'élément hors écran

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

Mouvement accessible

Tout le monde ne souhaite pas bénéficier d'une expérience de mouvement de glissement. Dans notre solution, cette préférence est appliquée en ajustant une variable CSS --duration dans une requête multimédia. Cette valeur de requête multimédia représente la préférence du système d'exploitation de l'utilisateur pour le mouvement (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 le panneau latéral s'ouvre et se ferme, si un utilisateur préfère réduire les mouvements, je déplace instantanément l'élément dans la vue, en conservant l'état sans mouvement.

Transition, transformation, traduction

Barre latérale ouverte (par défaut)

Pour définir l'état par défaut de notre panneau latéral 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 typique de -100vw pour m'assurer que le box-shadow de la barre latérale ne s'affiche pas dans le viewport principal 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);
  }
}
Barre latérale ouverte

Lorsque l'élément #sidenav correspond à :target, définissez la position translateX() sur la base de données 0, puis regardez le CSS faire glisser l'élément de sa position externe -110vw à sa position "in" 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 ouvert, afin que les systèmes ne mettent pas le focus sur un menu hors écran. Pour ce faire, je définis une transition de visibilité lorsque le :target change.

  • Lorsque vous entrez, ne faites pas de transition de visibilité. Soyez visible immédiatement pour que je puisse voir l'élément glisser et accepter la sélection.
  • Lorsque vous quittez l'écran, modifiez la visibilité de la transition, mais retardez-la pour qu'elle passe à hidden à la fin de la transition.

Améliorations de l'expérience utilisateur liées à 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 bénéficie de quelques fonctionnalités d'accessibilité intéressantes sans frais. Décorons nos éléments interactifs avec des libellés qui expriment 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 de la voix off et de l'interaction avec le clavier.

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

:is(:hover, :focus)

Ce pseudo-sélecteur fonctionnel CSS pratique nous permet d'être rapidement inclusif avec nos styles de survol en les partageant également avec la sélection.

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

Ajouter du JavaScript

Appuyez sur escape pour fermer

La touche Escape de votre clavier devrait fermer le menu, n'est-ce pas ? Allons-y.

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

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

Pour éviter que l'interaction d'ouverture et de fermeture ne mette en pile plusieurs entrées dans l'historique du navigateur, ajoutez le code JavaScript suivant en ligne au bouton de fermeture:

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

L'entrée de l'historique des URL est supprimée à la fermeture, ce qui donne l'impression que le menu n'a jamais été ouvert.

Focus UX

L'extrait suivant nous aide à mettre en surbrillance les boutons d'ouverture et de fermeture après leur ouverture ou fermeture. Je veux faciliter l'activation/la désactivation.

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

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

Lorsque le panneau latéral s'ouvre, sélectionnez le bouton de fermeture. Lorsque le panneau latéral se ferme, placez le focus sur le bouton d'ouverture. Pour ce faire, j'appelle focus() sur l'élément en JavaScript.

Conclusion

Maintenant que vous savez comment j'ai fait, comment pourriez-vous faire ? Cela permet d'obtenir une architecture de composants amusante. Qui va créer la première version avec des emplacements ? 🙂

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

Remix de la communauté