Creazione di un componente di navigazione laterale

Panoramica di base su come creare un riquadro laterale scorrevole adattabile

In questo post voglio condividere con te come ho creato il prototipo di un componente Sidenav per il web che è responsive, con stato, supporta la navigazione da tastiera, funziona con e senza JavaScript e su tutti i browser. Prova la demo.

Se preferisci i video, ecco una versione di questo post su YouTube:

Panoramica

È difficile creare un sistema di navigazione reattivo. Alcuni utenti useranno una tastiera, altri avranno computer desktop potenti e altri ancora accederanno da un piccolo dispositivo mobile. Chiunque visiti il tuo sito dovrebbe essere in grado di aprire e chiudere il menu.

Demo del layout adattabile da computer a dispositivo mobile
Tema chiaro e scuro disponibile su iOS e Android

Tattiche web

In questa esplorazione dei componenti ho avuto la gioia di combinare alcune funzionalità fondamentali della piattaforma web:

  1. CSS :target
  2. Griglia CSS
  3. Trasformazioni CSS
  4. Query supporti CSS per l'area visibile e le preferenze dell'utente
  5. JS per focus miglioramenti dell'esperienza utente

La mia soluzione ha una barra laterale e si attiva/disattiva solo quando l'area visibile "mobile" è pari o inferiore a 540px. 540px sarà il nostro punto di interruzione per passare dal layout interattivo mobile a quello statico desktop.

Pseudo classe CSS :target

Un link <a> imposta l'hash dell'URL su #sidenav-open e l'altro su vuoto (''). Infine, un elemento ha id per corrispondere all'hash:

<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>

Se fai clic su ciascuno di questi link, lo stato dell'hash dell'URL della pagina cambia, quindi con una pseudo-classe mostro e nascondo il riquadro laterale:

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

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

Griglia CSS

In passato utilizzavo solo layout e componenti di navigazione laterale con posizione assoluta o fissa. La griglia, invece, con la sua sintassi grid-area, consente di assegnare più elementi alla stessa riga o colonna.

Serie

L'elemento di layout principale #sidenav-container è una griglia che crea 1 riga e 2 colonne, 1 di ciascuna è denominata stack. Quando lo spazio è limitato, CSS assegna a tutti gli elementi figli dell'elemento <main> lo stesso nome della griglia, posizionando tutti gli elementi nello stesso spazio e creando una serie.

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

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

<aside> è l'elemento animato che contiene la barra di navigazione laterale. Ha 2 elementi secondari: il contenitore di navigazione <nav> denominato [nav] e uno sfondo <a> denominato [escape], che viene utilizzato per chiudere il menu.

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

Modifica 2fr e 1fr per trovare il rapporto che preferisci per l'overlay del menu e il relativo pulsante di chiusura con spazio negativo.

Una demo di cosa succede quando modifichi il formato.

Trasformazioni e transizioni CSS 3D

Il nostro layout è ora sovrapposto in base alle dimensioni dell'area visibile del dispositivo mobile. Finché non aggiungo nuovi stili, si sovrappone al nostro articolo per impostazione predefinita. Ecco alcune UX che cercherò nella prossima sezione:

  • Animazione di apertura e chiusura
  • Animare con il movimento solo se l'utente è d'accordo
  • Anima visibility in modo che lo stato attivo della tastiera non entri nell'elemento offscreen

Man mano che inizio a implementare le animazioni di movimento, voglio partire con la massima priorità sull'accessibilità.

Movimento accessibile

Non tutti vorranno un'esperienza di movimento scorrevole. Nella nostra soluzione, questa preferenza viene applicata modificando una variabile CSS --duration all'interno di una query sui media. Questo valore della query sui contenuti multimediali rappresenta la preferenza del sistema operativo dell'utente per il movimento (se disponibile).

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

@media (prefers-reduced-motion: reduce) {
  #sidenav-open {
    --duration: 1ms;
  }
}
Una demo dell'interazione con e senza durata applicata.

Ora, quando il riquadro laterale si apre e si chiude, se un utente preferisce ridurre il movimento, muovo immediatamente l'elemento in vista, mantenendo lo stato senza movimento.

Transizione, trasformazione e traslazione

Riquadro laterale chiuso (valore predefinito)

Per impostare lo stato predefinito del riquadro laterale sui dispositivi mobili su uno stato offscreen, posiziono l'elemento con transform: translateX(-110vw).

Tieni presente che ho aggiunto un altro 10vw al codice offscreen tipico di -100vw, per assicurarmi che il box-shadow del riquadro laterale non venga visualizzato nel viewport principale quando è nascosto.

@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);
  }
}
Sidenav in

Quando l'elemento #sidenav corrisponde a :target, imposta la posizione translateX() su homebase 0 e osserva come il CSS fa scorrere l'elemento dalla posizione out -110vw alla posizione "in" 0 su var(--duration) quando l'hash dell'URL viene modificato.

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

Visibilità della transizione

L'obiettivo ora è nascondere il menu agli screen reader quando non è attivo, in modo che i sistemi non mettano il focus su un menu offscreen. Posso farlo impostando una transizione di visibilità quando :target cambia.

  • Quando entri, non eseguire la transizione della visibilità; sii visibile subito in modo che io possa vedere l'elemento scorrere e accettare lo stato attivo.
  • Quando esci, esegui la transizione della visibilità, ma ritardala, in modo che passi a hidden al termine della transizione.

Miglioramenti all'esperienza utente per l'accessibilità

Questa soluzione si basa sulla modifica dell'URL per consentire la gestione dello stato. Naturalmente, qui deve essere utilizzato l'elemento <a>, che offre alcune utili funzionalità di accessibilità senza costi. Aggiungiamo ai nostri elementi interattivi delle etichette che ne esprimano chiaramente l'intenzione.

<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>
Una demo dell'esperienza utente di interazione con la tastiera e la voce fuori campo.

Ora i nostri pulsanti di interazione principali indicano chiaramente la loro intenzione sia per il mouse che per la tastiera.

:is(:hover, :focus)

Questo pratico pseudo-selettore CSS funzionale ci consente di essere rapidamente inclusivi con i nostri stili di passaggio del mouse condividendoli anche con lo stato attivo.

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

Sprinkle on JavaScript

Premi escape per chiudere

Il tasto Escape sulla tastiera dovrebbe chiudere il menu, giusto? Colleghiamolo.

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

sidenav.addEventListener('keyup', event => {
  if (event.code === 'Escape') document.location.hash = '';
});
Cronologia del browser

Per evitare che l'interazione di apertura e chiusura sovrapponga più voci nella cronologia del browser, aggiungi il seguente codice JavaScript incorporato al pulsante di chiusura:

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

La voce della cronologia degli URL verrà rimossa alla chiusura, come se il menu non fosse mai stato aperto.

Focus UX

Lo snippet successivo ci aiuta a impostare lo stato attivo sui pulsanti di apertura e chiusura dopo l'apertura o la chiusura. Voglio semplificare l'attivazione/disattivazione.

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

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

Quando si apre il riquadro laterale, metti in primo piano il pulsante di chiusura. Quando il riquadro laterale si chiude, lo stato attivo viene visualizzato sul pulsante Apri. Lo faccio chiamando focus() sull'elemento in JavaScript.

Conclusione

Ora che sai come ho fatto, come faresti? Si tratta di un'architettura di componenti molto interessante. Chi realizzerà la prima versione con le fessure? 🙂

Diversifichiamo i nostri approcci e scopriamo tutti i modi per creare sul web. Crea un glitch, twittami la tua versione e la aggiungerò alla sezione Remix della community di seguito.

Remix della community