Creazione di un componente di navigazione laterale

Una 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 adattabile. Alcuni utenti utilizzeranno una tastiera, alcuni avranno computer potenti e altri visiteranno il sito da un piccolo dispositivo mobile. Tutti i visitatori devono essere in grado di aprire e chiudere il menu.

Demo del layout responsive da desktop a mobile
Temi chiaro e scuro non disponibili 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 abbinare l'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 uno 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 della barra laterale con posizione assoluta o fissa. Tuttavia, la griglia, 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 due elementi secondari: il contenitore di navigazione <nav> denominato [nav] e un'immagine di sfondo <a> denominata [escape], che viene utilizzata 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, viene sovrapposto al nostro articolo per impostazione predefinita. Ecco alcuni aspetti dell'esperienza utente che intendo realizzare 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

Quando inizio a implementare le animazioni in movimento, voglio iniziare tenendo presente l'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 applicazione della durata.

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

Riquadro laterale chiuso (impostazione predefinita)

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 lo stato attivo su un menu offscreen. Lo faccio 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 gestire lo 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 esprimono 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? Collega i cavi.

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 in linea 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 mettere a fuoco i pulsanti Apri e Chiudi dopo che si aprono o si chiudono. Voglio semplificare il passaggio da un'opzione all'altra.

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