Présentation générale de la création d'un panneau de navigation latéral glissant et responsif
Dans cet article, je souhaite vous expliquer comment j'ai prototypé un composant Sidenav pour le Web réactif, avec état, compatible avec la navigation au clavier, compatible avec et sans JavaScript et compatible avec tous les navigateurs. Essayez la démonstration.
Si vous préférez la vidéo, voici une version YouTube de cet article:
Présentation
Créer un système de navigation réactif est difficile. Certains utilisateurs auront un clavier, d'autres un ordinateur de bureau puissant et d'autres y accéderont à partir d'un petit appareil mobile. Tous les visiteurs devraient pouvoir ouvrir et fermer le menu.
Tactiques Web
Pour cette exploration des composants, j'ai eu le plaisir de combiner quelques fonctionnalités essentielles de la plate-forme Web:
- CSS
:target
- Grille CSS
- transforms CSS
- Requêtes média CSS pour la fenêtre d'affichage et les préférences de l'utilisateur
- JS pour
focus
améliorations de l'expérience utilisateur
Ma solution comporte une barre latérale et ne s'active que lorsque la fenêtre d'affichage "mobile" ne dépasse pas 540px
.
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 de bureau.
Pseudo-classe CSS :target
Un lien <a>
définit le hachage de l'URL sur #sidenav-open
et l'autre sur vide (''
). Enfin, un élément possède l'id
qui correspond 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, vous modifiez l'état de hachage de l'URL de la page. Avec une pseudo-classe, j'affiche et je masque le panneau de navigation 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 navigation latérale à position absolue ou fixe. Cependant, la grille, avec sa syntaxe grid-area
, 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, dont une est nommée stack
. Lorsque l'espace est limité, CSS attribue tous les enfants de l'élément <main>
au même nom de grille, ce qui place 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;
}
}
Arrière-plan du menu
<aside>
est l'élément d'animation qui contient la barre de 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 format souhaité pour la superposition du menu et son bouton de fermeture d'espace négatif.
Transformations et transitions 3D CSS
Notre mise en page est désormais empilée dans une fenêtre d'affichage mobile. Tant que j'ai pas ajouté de nouveaux styles, il se superpose à notre article par défaut. Voici quelques exemples UX que je vise dans la section suivante:
- Animer l'ouverture et la fermeture
- N'animez l'animation avec des mouvements que si l'utilisateur est d'accord.
- Animer
visibility
pour que le curseur ne passe pas dans l'élément hors écran
Lorsque je commence à implémenter des animations de mouvement, je veux commencer par l’accessibilité en tête.
Mouvement accessible
Tout le monde ne voudra pas une expérience de mouvement coulissante. 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 la préférence d'un utilisateur pour les mouvements (le cas échéant) du système d'exploitation.
#sidenav-open {
--duration: .6s;
}
@media (prefers-reduced-motion: reduce) {
#sidenav-open {
--duration: 1ms;
}
}
Désormais, lorsque l'utilisateur glisse pour ouvrir ou fermer le panneau de navigation latéral, 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
Sortie du panneau latéral (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 habituel de -100vw
, pour s'assurer que le box-shadow
de la navigation latérale n'apparaît pas dans la fenêtre d'affichage principale lorsqu'elle est masquée.
@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 translateX()
sur 0
(base d'accueil) et observez le fait que le CSS fait glisser l'élément de sa position hors -110vw
à sa position "in" 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 des lecteurs d'écran lorsqu'il est fermé, afin que les systèmes ne placent pas le curseur dans un menu hors écran. Pour ce faire, je définis une transition de visibilité lorsque :target
change.
- Lors de l'entrée, ne changez pas la visibilité ; soyez visible immédiatement afin que je puisse voir l'élément glisser vers l'intérieur et accepter le focus.
- Lorsque vous sortez, la visibilité de la transition est retardée, de sorte qu'elle bascule sur
hidden
à la fin de la transition.
Améliorations apportées à l'accessibilité de l'expérience utilisateur
Liens
Cette solution repose sur la modification de l'URL afin que l'état soit géré.
Naturellement, l'élément <a>
doit être utilisé ici. Il permet d'obtenir sans frais des fonctionnalités d'accessibilité intéressantes. Ajoutons à nos éléments interactifs des étiquettes 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ésormais, nos principaux boutons d'interaction indiquent clairement leur utilité pour la souris et pour le clavier.
:is(:hover, :focus)
Ce pseudo-sélecteur fonctionnel CSS 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, n'est-ce pas ? Raccordons-nous au câble.
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 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 de l'URL sera supprimée lors de sa fermeture, faisant comme si le menu n'était jamais ouvert.
Cibler l'expérience utilisateur
L'extrait de code suivant nous aide à mettre l'accent sur les boutons d'ouverture et de fermeture une fois qu'ils s'ouvrent ou se ferment. Je veux faciliter l'activation ou 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 de navigation latéral s'ouvre, sélectionnez le bouton de fermeture. Lorsque le panneau de navigation latéral
se ferme, sélectionnez 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 faire ?! Cela permet d'obtenir une architecture de composants amusante. Qui va créer la première version avec des emplacements ? 🙂
Diversifiez nos approches et découvrons toutes les méthodes de développement sur le Web. Créez un Glitch, envoyez-moi un tweet pour votre version et je l'ajouterai à la section Remix de la communauté ci-dessous.
Remix de la communauté
- @_developit avec des éléments personnalisés: demo & code
- @mayeedwin1 avec HTML/CSS/JS: démonstration et code
- @a_nurella avec un remix Glitch: démonstration et code
- @EvroMalarkey avec HTML/CSS/JS: démonstration et code