Creazione di un componente di navigazione laterale

Una panoramica di base su come creare una sidenav estraibile adattabile

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

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

Panoramica

Creare un sistema di navigazione reattivo è difficile. Alcuni utenti utilizzeranno una tastiera, altri avranno computer desktop potenti e altri ancora visiteranno il sito da un piccolo dispositivo mobile. Tutti i visitatori devono essere in grado di aprire e chiudere il menu.

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

Tattiche web

In questa esplorazione dei componenti ho avuto il piacere di combinare alcune funzionalità critiche 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 sola barra laterale e si attiva solo quando si trova in un'area visibile "mobile" di 540px o meno. 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 hash dell'URL della pagina cambia. Poi, con una pseudo-classe, mostro e nascondo la barra di navigazione laterale:

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

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

Griglia CSS

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

Impilati

L'elemento di layout principale #sidenav-container è una griglia che crea una riga e due colonne, una delle quali è denominata stack. Quando lo spazio è limitato, CSS assegna tutti gli elementi secondari dell'elemento <main> allo stesso nome della griglia, posizionando tutti gli elementi nello stesso spazio e creando una pila.

#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 navigazione laterale. Ha due elementi secondari: il contenitore di navigazione <nav> denominato [nav] e uno sfondo <a> denominato [escape], utilizzato per chiudere il menu.

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

Regola 2fr e 1fr per trovare le proporzioni che preferisci per l'overlay del menu e il pulsante di chiusura dello spazio negativo.

Una demo di ciò che accade quando modifichi il rapporto.

Trasformazioni e transizioni 3D CSS

Il layout è ora impilato in base alle dimensioni dell'area visibile mobile. Finché non aggiungo nuovi stili, si sovrappone al nostro articolo per impostazione predefinita. Ecco alcuni aspetti dell'esperienza utente che voglio ottenere in questa sezione:

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

Quando inizio a implementare le animazioni di movimento, voglio dare la priorità all'accessibilità.

Movimento accessibile

Non tutti vorranno un'esperienza di movimento a scorrimento. Nella nostra soluzione questa preferenza viene applicata modificando una variabile CSS --duration all'interno di una media query. Questo valore della media query rappresenta la preferenza del sistema operativo di un 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 la barra di navigazione laterale si apre e si chiude, se un utente preferisce il movimento ridotto, sposto immediatamente l'elemento in visualizzazione, mantenendo lo stato senza movimento.

Transizione, trasformazione, traduzione

Sidenav out (predefinito)

Per impostare lo stato predefinito della barra di navigazione laterale sui dispositivi mobili su uno stato fuori schermo, posiziono l'elemento con transform: translateX(-110vw).

Ho aggiunto un altro 10vw al codice tipico fuori schermo di -100vw, per assicurarmi che box-shadow della barra di navigazione laterale non sbirci nella finestra principale quando è nascosta.

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

Quando l'elemento #sidenav corrisponde a :target, imposta la posizione di translateX() sulla base 0, e osserva come il CSS fa scorrere l'elemento dalla posizione di uscita -110vw alla posizione di entrata 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 dai lettori di schermo quando non è visibile, in modo che i sistemi non mettano il focus su un menu fuori dallo schermo. Per farlo, imposto una transizione di visibilità quando cambia :target.

  • Quando entri, non eseguire la transizione della visibilità; sii visibile immediatamente 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 alla fine della transizione di uscita.

Miglioramenti dell'esperienza utente per l'accessibilità

Questa soluzione si basa sulla modifica dell'URL per gestire lo stato. Naturalmente, qui deve essere utilizzato l'elemento <a>, che offre senza costi alcune utili funzionalità di accessibilità. Decoriamo i nostri elementi interattivi con etichette che esprimano chiaramente l'intento.

<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 il loro scopo sia per il mouse che per la tastiera.

:is(:hover, :focus)

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

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

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

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

UX di Focus

Il seguente snippet ci aiuta a mettere a fuoco i pulsanti di apertura e chiusura dopo che si aprono o si chiudono. Voglio semplificare l'attivazione e la 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 di navigazione laterale, sposta lo stato attivo sul pulsante di chiusura. Quando la barra di navigazione laterale si chiude, sposta lo stato attivo sul pulsante di apertura. A tale scopo, chiamo focus() sull'elemento in JavaScript.

Conclusione

Ora che sai come ho fatto, come faresti tu? In questo modo si crea un'architettura dei componenti divertente. Chi realizzerà la prima versione con gli slot? 🙂

Diversifichiamo i nostri approcci e impariamo tutti i modi per creare contenuti sul web. Crea un Glitch, inviami un tweet con la tua versione e la aggiungerò alla sezione Remix della community di seguito.

Remix della community