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.
Tattiche web
In questa esplorazione dei componenti ho avuto la gioia di combinare alcune funzionalità fondamentali della piattaforma web:
- CSS
:target
- Griglia CSS
- Trasformazioni CSS
- Query supporti CSS per l'area visibile e le preferenze dell'utente
- 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;
}
}
Sfondo del menu
<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.
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;
}
}
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à
Link
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>
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
- @_developit con elementi personalizzati: demo e codice
- @mayeedwin1 con HTML/CSS/JS: demo e codice
- @a_nurella con un glitch remix: demo e codice
- @EvroMalarkey con HTML/CSS/JS: demo e codice