Doxa /

Une histoire à tiroirs (2)

Labo

J’étais très fier de mon histoire de tiroir… jusqu’à ce que la tendinite qui annonce le bouclage me force à lâcher mon trackball. L’astuce généralement employée pour concevoir un menu « hamburger » en code CSS consiste à utiliser une case à cocher. Son libellé — en l’occurrence — est visible, et peut donc être manipulé avec le pointeur, mais la case elle-même est masquée, et ne peut donc pas être manipulée avec le clavier :

<input hidden id="menu-opener" type="checkbox" />
<label for="menu-opener" role="button"></label>

L’approche plus traditionnelle est plus immédiatement accessible, puisqu’elle utilise un simple bouton, mais demande de régler les interactions avec quelques lignes de code JavaScript. Le script « attend » le clic sur le bouton pour déclencher la fonction menu_toggle(), qui relève la valeur de l’attribut menu-state pour savoir si le menu est ouvert ou fermé, et donc s’il doit être fermé ou ouvert :

var menu = document.getElementById("menu");
var menu_button = document.getElementById("menu-button");

function menu_open() {
  menu.setAttribute("menu-state", "opened");
}

function menu_close() {
  menu.setAttribute("menu-state", "closed");
}

function menu_toggle() {
  var menu_state = menu.getAttribute("menu-state");

  if (menu_state == "closed") {
    menu_open();
  } else {
    menu_close();
  }
}

menu_button.addEventListener("click", function () {
  menu_toggle();
}, false);

Ces changements favorisent l’amélioration progressive du code CSS en fonction des capacités du navigateur. Sur tous les navigateurs de la dernière décennie, la valeur de l’attribut menu-state détermine l’apparition du menu :

.menu {
  /* Par défaut, le menu est fermé et donc caché. */
  position: absolute;
  transform: translateY(125%);
  visibility: hidden;
}

.menu[menu-state="opened"] {
  /* Le menu est ouvert et donc affiché. */
  position: fixed;
    bottom: 4em;
    left: 0;
    right: 0;
    top: 0;
  transform: translateY(0);
  visibility: inherit;
}

Sur les navigateurs plus récents, le sélecteur :has() et le sélecteur de voisin général ~ permettent de voiler et flouter l’arrière-plan lorsque le menu est ouvert :

.site-header .overlay {
  /* Par défaut, le voile est invisible. */
  opacity: 0;
}

.site-header:has(.menu[menu-state="opened"]) .overlay {
  /* Lorsque le menu est ouvert,
  le voile est visible. */
  background: var(--bg-overlay);
  position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    top: 0;
  opacity: 1;
  z-index: 1;
}

.site-header:has(.menu[menu-state="opened"]) ~ .site-main {
  /* Lorsque le menu est ouvert,
  les éléments voisins sont floutés. */
  filter: blur(5px);
}

L’API Pop Up, qui vient tout juste d’être incorporée à Google Chrome, promet d’incorporer ces mécanismes au navigateur. Le progrès a (parfois) du bon. Quitte à devoir écrire du code JavaScript, j’ai ajouté quelques lignes supplémentaires pour encore simplifier la navigation au clavier. Une pression sur la touche M permet d’ouvrir le menu, et une pression sur la touche de le fermer, voilà pourquoi j’avais prévu des fonctions menu_open() et menu_close() plutôt qu’une seule fonction menu_toggle() :

document.addEventListener('keydown', function(event) {
  if (event.code === "KeyM") {
    menu_open();
  }

  if (event.key === "Escape") {
    menu_close();
  }
}, false);

J’ai profité de l’occasion pour personnaliser l’apparence de la mise en évidence :focus, en reprenant l’astuce de Patrick Lauke, qui permet de distinguer les navigateurs prenant uniquement en charge :focus (qui peut apparaitre lors des manipulations avec le pointeur) des navigateurs prenant aussi en charge :focus-visible (qui apparait uniquement lors des manipulations avec le clavier) :

*:focus {
  /* La mise en évidence par défaut… */
  outline: 2px solid var(--bg-primary);
  outline-offset: 2px;
}

*:focus:not(:focus-visible) {
  /* …est annulée dans les navigateurs
  prenant en charge :focus-visible… */
  outline: none;
  outline-offset: 0;
}

*:focus-visible {
  /* …et réservée aux manipulations
  avec le clavier. */
  outline: 2px solid var(--bg-primary);
  outline-offset: 2px;
}

Le contour est parfois coupé quand les dimensions des éléments ne sont pas explicites. La solution n’est pas très satisfaisante, mais est parfaitement fonctionnelle :

*:focus-visible {
  position: relative;
}

Le menu de navigation n’est plus très loin de ce que j’avais en tête, je vais maintenant pouvoir me concentrer sur la mise en page des articles.