Creazione di un componente di navigazione laterale

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

In questo post vorrei condividere con te come ho prototipato un componente Sidenav per il web che sia reattivo, stateful, supporti la navigazione da tastiera, funzioni con e senza JavaScript e funzioni su tutti i browser. Prova la demo.

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

Panoramica

È difficile creare un sistema di navigazione reattivo. Alcuni utenti useranno una tastiera, altri un computer potente e altri ancora un piccolo dispositivo mobile. Chiunque visiti il sito deve essere in grado di aprire e chiudere il menu.

Demo del layout adattabile da computer a dispositivi mobili
Tema chiaro e scuro disattivato su iOS e Android

Tattiche web

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

  1. CSS :target
  2. Griglia di CSS
  3. CSS transforms
  4. Query multimediali CSS per area visibile e preferenze degli utenti
  5. JS per focus miglioramenti UX

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

Pseudo-classe :target CSS

Un link <a> imposta l'hash dell'URL su #sidenav-open e l'altro su vuoto (''). Infine, un elemento ha id per trovare corrispondenze con 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 ciascuno di questi link viene modificato lo stato hash dell'URL della nostra pagina, dopodiché mostro e nascondo la barra laterale con una pseudo-classe:

@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 o con posizione fissa. La griglia, però, con la sintassi grid-area, consente di assegnare più elementi alla stessa riga o colonna.

Impilati

L'elemento principale del layout #sidenav-container è una griglia che crea una riga e due colonne, di cui una è denominata stack. Quando lo spazio è vincolato, CSS assegna a tutti gli elementi secondari dell'elemento <main> lo stesso nome di 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 barra di navigazione laterale. Ha 2 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 cosa succede quando modifichi il rapporto.

Trasformazioni e transizioni 3D CSS

Il nostro layout ora include le dimensioni dell'area visibile su dispositivi mobili. Fino a quando non aggiungo nuovi stili, l'articolo viene sovrapposto per impostazione predefinita. Ecco alcune UX che sto cercando nella prossima sezione:

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

Quando inizio a implementare le animazioni in movimento, vorrei dare l'importanza all'accessibilità.

Movimenti accessibili

Non tutti vogliono avere un'esperienza di movimento a scorrimento. Nella nostra soluzione, questa preferenza viene applicata modificando una variabile CSS --duration all'interno di una query supporti. Questo valore di 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 barra di navigazione laterale scorre in modo che sia aperta e chiusa, se un utente preferisce un movimento ridotto, sposto subito l'elemento in vista, mantenendo lo stato senza movimento.

Transizione, trasformazione, traduzione

Navigazione laterale in uscita (impostazione predefinita)

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

Nota: ho aggiunto un altro 10vw al codice tipico fuori schermo di -100vw, per assicurarmi che box-shadow della barra di navigazione laterale non sbirci nell'area visibile 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 translateX() sul homebase 0 e osserva come CSS far scorrere l'elemento dalla sua posizione esterna 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

L'obiettivo ora è nascondere il menu agli screen reader quando non è disponibile, in modo che i sistemi non impostino lo stato attivo su un menu fuori schermo. A questo scopo, imposto una transizione di visibilità quando cambia :target.

  • Quando entrerai, 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, la visibilità della transizione viene ritardata, quindi passa a hidden alla fine della transizione.

Miglioramenti dell'UX per l'accessibilità

Questa soluzione si basa sulla modifica dell'URL per gestire lo stato. Ovviamente, l'elemento <a> dovrebbe essere utilizzato qui e ottiene senza costi alcune utili funzioni di accessibilità. Abbelliamo i nostri elementi interattivi con etichette che esprimono 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 con l'interazione con la voce fuori campo e la tastiera.

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 ci consente di essere rapidamente inclusivi con gli stili al passaggio del mouse, condividendoli anche in modo specifico.

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

Cospargere su JavaScript

Premi escape per chiudere

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

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

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

Per impedire che l'interazione di apertura e chiusura accumuli 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, rendendola come se il menu non fosse mai stato aperto.

Focus UX

Lo snippet successivo ci aiuta a mettere in evidenza i pulsanti di apertura e chiusura dopo l'apertura o la chiusura. Voglio semplificare l'attivazione.

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 lo stato attivo sul pulsante di chiusura. Quando la barra di navigazione laterale si chiude, attiva il pulsante Apri. Per farlo, chiamo focus() sull'elemento in JavaScript.

Conclusione

Ora che sai come ci sono riuscito, come faresti?! Questo rende l'architettura dei componenti divertente. Chi realizzerà la prima versione con gli slot? 🙂

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

Remix della community