Creazione di un componente di navigazione laterale

Una panoramica di base su come creare una barra di navigazione laterale adattabile a scomparsa

In questo post voglio condividere con te il modo in cui ho creato il prototipo di un componente Sidenav per il web che è adattabile, 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 adattabile è 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 adattabile da computer a dispositivo mobile
Tema chiaro e scuro su iOS e Android

Tattiche per il 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 della UX

La mia soluzione ha una barra laterale e si attiva/disattiva 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 per dispositivi mobili al layout statico per computer.

Pseudo-classe CSS :target

Un link <a> imposta l'hash dell'URL su #sidenav-open e l'altro su vuoto (''). Infine, un elemento ha l'attributo 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 la navigazione 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. Tuttavia, la griglia, 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 1 riga e 2 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 uno stack.

#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], che viene utilizzato per chiudere il menu.

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

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

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

Trasformazioni e transizioni CSS 3D

Il nostro layout è ora impilato in base alle dimensioni dell'area visibile per dispositivi mobili. Finché non aggiungo nuovi stili, per impostazione predefinita si sovrappone al nostro articolo. Ecco alcune UX che sto cercando di ottenere in questa sezione:

  • Animazione di apertura e chiusura
  • Anima con movimento solo se l'utente è d'accordo
  • Anima visibility in modo che la selezione della tastiera non entri nell'elemento fuori schermo

Quando inizio a implementare le animazioni di movimento, voglio iniziare con l'accessibilità in primo piano.

Movimento accessibile

Non tutti vorranno un'esperienza di movimento con scorrimento. Nella nostra soluzione, questa preferenza viene applicata modificando una variabile CSS --duration all'interno di una query supporti. Questo valore della query supporti 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 navigazione laterale si apre e si chiude, se un utente preferisce un movimento ridotto, sposto istantaneamente l'elemento nella visualizzazione, mantenendo lo stato senza movimento.

Transizione, trasformazione, traduzione

Sidenav out (predefinito)

Per impostare lo stato predefinito della barra di navigazione laterale su un dispositivo mobile in modo che sia fuori schermo, posiziono l'elemento con transform: translateX(-110vw).

Nota: ho aggiunto un altro 10vw al codice fuori schermo tipico di -100vw, per assicurarmi che l'box-shadow della navigazione laterale non venga visualizzato nel riquadro visibile 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() sulla base 0 e osserva come il CSS fa scorrere l'elemento dalla posizione "out" di -110vw alla posizione "in" di 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

Ora l'obiettivo è nascondere il menu agli screen reader quando è chiuso, in modo che i sistemi non spostino lo stato attivo su un menu fuori schermo. Per farlo, imposto una transizione di visibilità quando :target cambia.

  • Quando entri, non cambiare la visibilità; rendila visibile immediatamente in modo che io possa vedere l'elemento scorrere e accettare lo stato attivo.
  • Quando esci, applica la transizione alla visibilità, ma ritardala, in modo che passi a hidden alla fine della transizione in uscita.

Miglioramenti della UX per l'accessibilità

Questa soluzione si basa sulla modifica dell'URL per la gestione dello stato. Naturalmente, in questo caso deve essere utilizzato l'elemento <a>, che offre alcune utili funzionalità di accessibilità senza costi. Arricchiamo i nostri elementi interattivi con etichette che ne esprimano chiaramente lo scopo.

<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 della UX 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 includere rapidamente gli stili di passaggio del mouse condividendoli anche con lo stato attivo.

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

Aggiungi 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 accumuli più voci nella cronologia di navigazione 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 incentrata sull'utente

Lo snippet successivo ci aiuta a spostare lo stato attivo sui pulsanti di apertura e chiusura dopo che vengono aperti o chiusi. 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 la barra di navigazione laterale, attiva il pulsante di chiusura. Quando la barra di navigazione laterale si chiude, lo stato attivo passa al pulsante di apertura. Per farlo, chiamo focus() sull'elemento in JavaScript.

Conclusione

Ora che sai come ho fatto io, come faresti tu? Questo rende l'architettura dei componenti piuttosto divertente. Chi realizzerà la prima versione con gli slot? 🙂

Diversifichiamo i nostri approcci e scopriamo tutti i modi per creare 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