Atelier de programmation: Créer un composant Sidenav

Cet atelier de programmation vous explique comment créer un composant de mise en page de navigation latérale coulissante responsive sur le Web. Nous allons créer le composant au fur et à mesure, en commençant par le code HTML, puis le CSS, puis le JavaScript.

Consultez mon article de blog Créer un composant Sidenav pour en savoir plus sur les fonctionnalités de la plate-forme Web CSS choisies pour créer ce composant.

Configuration

  1. Cliquez sur Remixer pour modifier pour rendre le projet modifiable.
  2. Ouvrez app/index.html.

HTML

Commencez par obtenir les éléments essentiels de la configuration HTML afin de disposer de contenu et de quelques cases à utiliser.

Placez le code HTML suivant dans la balise <body>.

<aside></aside>
<main></main>

<aside> contient le menu de navigation en tant qu'élément complémentaire de <main>, qui contient le contenu principal de la page.

Nous allons ensuite remplir ces éléments sémantiques avec le reste du contenu de la page.

Ajoutez un élément de navigation, des liens de navigation et un lien de fermeture dans l'élément <aside>.

<aside>
  <nav>
    <h4>My</h4>
    <a href="#">Dashboard</a>
    <a href="#">Profile</a>
    <a href="#">Preferences</a>
    <a href="#">Archive</a>

    <h4>Settings</h4>
    <a href="#">Accessibility</a>
    <a href="#">Theme</a>
    <a href="#">Admin</a>
  </nav>

  <a href="#"></a>
</aside>

Les liens sont parfaits dans les éléments <nav>, et les éléments <nav> sont parfaits dans les barres latérales <aside>. Nous pouvons toutefois encore nous améliorer.

Dans l'élément de contenu principal, ajoutez un en-tête et un article pour contenir sémantiquement le contenu de la mise en page.

<main>
  <header>
    <a href="#sidenav-open" class="hamburger">
      <svg viewBox="0 0 50 40">
        <line x1="0" x2="100%" y1="10%" y2="10%" />
        <line x1="0" x2="100%" y1="50%" y2="50%" />
        <line x1="0" x2="100%" y1="90%" y2="90%" />
      </svg>
    </a>
    <h1>Site Title</h1>
  </header>

  <article>
    {put some placeholder content here}
  </article>
</main>

L'en-tête contient le lien d'ouverture du menu. Le panneau latéral comporte le bouton de fermeture. Nous allons bientôt afficher et masquer des éléments en fonction de la taille de la fenêtre d'affichage.

Dans l'élément <article>, nous avons collé une phrase d'espace réservé. Remplacez "``" par votre propre contenu ou collez le texte "lorem" fourni ci-dessous:

<h2>Totam Header</h2>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Cum consectetur, necessitatibus velit officia ut impedit veritatis temporibus soluta? Totam odit cupiditate facilis nisi sunt hic necessitatibus voluptatem nihil doloribus! Enim.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

<h3>Subhead Totam Odit</h3>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

<h3>Subhead</h3>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

Ce contenu et sa longueur permettent de faire défiler la page lorsqu'elle dépasse la hauteur de votre fenêtre d'affichage.

Jusqu'à présent, vous avez ajouté un élément "aside", avec un panneau de navigation, des liens et un moyen de fermer le panneau de navigation. Vous avez également ajouté un en-tête, un moyen d'ouvrir le panneau latéral et un article à l'élément principal. Cette approche est déjà claire, sémantique et intemporelle, mais nous pouvons la rendre plus claire pour tous. Le lien ouvert dans le panneau latéral pourrait être plus clairement indiqué.

Ajoutez les attributs title et aria-label à l'élément de lien ouvert de l'en-tête :

<a href="#sidenav-open" class="hamburger">
<a href="#sidenav-open" title="Open Menu" aria-label="Open Menu" class="hamburger">

L'icône SVG ouverte pourrait également être plus clairement marquée. Ajoutez les attributs suivants au SVG dans l'élément de lien ouvert :

<svg viewBox="0 0 50 40">
<svg viewBox="0 0 50 40" role="presentation" focusable="false" aria-label="trigram for heaven symbol">

Le lien de fermeture dans le panneau latéral pourrait être plus clairement marqué. Ajoutez les attributs title et aria-label à l'élément de lien de fermeture de navigation latérale:

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

CSS

Il est temps de mettre en page les éléments. Le contenu principal et le panneau latéral sont des enfants directs de la balise <body>. C'est donc un bon point de départ.

Ajoutez le code CSS suivant dans css/sidenav.css pour que l'élément <body> mette en page les enfants.

body {
  display: grid;
  grid: [stack] 1fr / min-content [stack] 1fr;

  @media (max-width: 540px) {
    & > :matches(aside, main) {
      grid-area: stack;
    }
  }
}

Cette mise en page indique essentiellement : créez une ligne nommée stack contenant tout, et deux colonnes dans cette ligne, dont la deuxième est également nommée stack. La première colonne doit être dimensionnée en fonction de ses besoins minimaux en contenu, et la deuxième colonne peut occuper le reste. Ensuite, si la fenêtre d'affichage est limitée à 540px ou moins, placez le panneau latéral et les éléments de contenu principal sur la même ligne et dans la même colonne, ce qui les superpose dans une grille de 1 x 1.

Avec cette fonctionnalité d'empilement responsif comme base, nous pouvons désormais exploiter l'état de la barre d'URL pour activer/désactiver la visibilité et le style de transition du panneau latéral.

Mettez à jour l'élément <aside> dans app/index.html:

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

Cela permet au CSS d'établir une correspondance entre un élément et le hachage de l'URL. Cela est important pour l'utilisation de :target. L'ID de l'élément peut désormais correspondre au hachage d'URL que nous allons définir avec des balises <a>.

De plus, pour faciliter le ciblage JavaScript, ajoutez des ID pour les éléments clés qui contrôlent le panneau latéral. Tout d'abord, ajoutez un ID au lien d'ouverture du panneau latéral:

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

Ajoutez ensuite un ID au lien de fermeture du panneau de navigation latéral:

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

Cela termine la mise en page d'empilement responsive de la macro <body> et nous relie à la barre d'URL. Continuons.

La mise en page de <aside> est aussi soignée. Il comporte deux enfants, un <nav>, qui est le composant qui ressemble à du papier qui glisse vers l'extérieur, et un élément de lien <a> de fermeture qui définit l'URL sur #. Le lien est invisible à droite de la barre de navigation glissante du papier. Il permet aux utilisateurs de "cliquer" sur le composant visuel pour le fermer.

Ajoutez le code CSS suivant à css/sidenav.css:

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

Je trouve que le format et les noms sont une très belle touche ici, où la grille peut briller et donner au concepteur beaucoup de contrôle.

Ensuite, je dois superposer de manière conditionnelle le contenu principal et conserver ma position lors du défilement du document. C'est une tâche idéale pour position: sticky et certains overscroll-behavior.

Ajoutez les styles suivants pour le panneau latéral:

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

  @media (max-width: 540px) {
    position: sticky;
    top: 0;
    max-height: 100vh;
    overflow: hidden auto;
    overscroll-behavior: contain;

    visibility: hidden; /* not keyboard accessible when closed */
  }
}

Ces styles garantissent que la barre latérale a la hauteur de la fenêtre d'affichage, qu'elle défile verticalement et qu'elle contient le défilement. Plus important encore, il masque l'élément. Par défaut, lorsque la fenêtre d'affichage est inférieure ou égale à 540px, masquez cette navigation latérale. Sauf !

Ajoutez un pseudo-sélecteur :target à l'élément #sidenav-open:

#sidenav-open {

  @media (max-width: 540px) {

    &:target {
      visibility: visible;
    }
  }
}

Lorsque l'ID de cet élément et de la barre d'URL sont identiques, définissez visibility sur visible. Ouvrez le menu latéral après avoir fait défiler la page, ou essayez de faire défiler la page lorsque le panneau latéral est ouvert. Qu'en pensez-vous ?

Ajoutez le code CSS suivant en bas de app/sidenav.css:

#sidenav-button,
#sidenav-close {
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  user-select: none;
  touch-action: manipulation;

  @media (min-width: 540px) {
    display: none;
  }
}

Ces styles ciblent nos boutons d'ouverture et de fermeture, spécifient leurs styles de pression et de contact, et les masquent également lorsque les vues d'affichage sont de 540px ou plus.

Pour ajouter un peu de style, ajoutons des transformations CSS tout en respectant l'accessibilité. Ajoutez le code CSS suivant à css/sidenav.css:

#sidenav-open {
  --easeOutExpo: cubic-bezier(0.16, 1, 0.3, 1);
  --duration: .6s;

  ...

  @media (max-width: 540px) {
    ...

    transform: translateX(-110vw);
    will-change: transform;
    transition:
      transform var(--duration) var(--easeOutExpo),
      visibility 0s linear var(--duration);

    &:target {
      visibility: visible;
      transform: translateX(0);
      transition: transform var(--duration) var(--easeOutExpo);
    }
  }

  @media (prefers-reduced-motion: reduce) {
    --duration: 1ms;
  }
}
Démonstration de l'interaction avec et sans durée appliquée, basée sur la requête multimédia "prefers-reduced-motion".

Ajouter du code JavaScript

La touche Escape devrait fermer le menu. Ajoutez le code JavaScript suivant à js/index.js:

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

sidenav.addEventListener('keyup', e => {
  if (e.code === 'Escape') {
    document.location.hash = '';
  }
});

Il écoute un événement de touche sur l'élément de panneau latéral. Si la valeur est Escape, le hachage de l'URL est vide, ce qui entraîne la suppression de la navigation latérale.

La partie suivante de l'UX JS concerne la gestion de la sélection. Je souhaite faciliter l'ouverture et la fermeture. Je patiente donc jusqu'à ce que le panneau latéral ait terminé une transition, puis je le vérifie par rapport au hachage de l'URL pour déterminer s'il est ouvert ou fermé. J'utilise ensuite JavaScript pour définir la sélection sur le bouton complémentaire de celui sur lequel l'utilisateur vient d'appuyer.

Ajoutez le code JavaScript suivant à js/index.js:

const closenav = document.querySelector('#sidenav-close');
const opennav = document.querySelector('#sidenav-button');

sidenav.addEventListener('transitionend', e => {
  if (e.propertyName !== 'transform') {
    return;
  }

  const isOpen = document.location.hash === '#sidenav-open';

  isOpen
    ? closenav.focus()
    : opennav.focus();
});

Essayer

  • Pour prévisualiser le site, appuyez sur Afficher l'application, puis sur Plein écran plein écran.

Conclusion

C'est tout pour les besoins que j'avais concernant le composant. N'hésitez pas à l'utiliser, à le piloter avec l'état JavaScript au lieu de l'URL et, en général, à l'adapter à vos besoins. Il y a toujours plus à ajouter ou plus de cas d'utilisation à couvrir.

Ouvrez css/brandnav.css pour vérifier les styles non liés à la mise en page que j'ai appliqués à ce composant. Je ne pensais pas que cela était important pour l'ensemble de fonctionnalités sur lequel je me concentrais, et j'espérais que la séparation des styles de la mise en page encouragerait le copier-coller. Vous pourriez en apprendre davantage !

Comment créer des composants de navigation latérale responsifs ? Avez-vous déjà eu plus d'un, par exemple un de chaque côté ? J'aimerais présenter votre solution dans une vidéo YouTube. N'hésitez pas à m'envoyer un tweet ou à commenter sur YouTube en indiquant votre code. Cela aidera tout le monde.