Eine grundlegende Übersicht über das Erstellen einer responsiven, ausklappbaren Seitennavigation
In diesem Beitrag möchte ich Ihnen zeigen, wie ich einen Prototyp für eine Sidenav-Komponente für das Web erstellt habe, die responsiv und zustandsorientiert ist, die Tastaturnavigation unterstützt, mit und ohne JavaScript funktioniert und browserübergreifend ist. Demo ausprobieren
Wenn Sie sich lieber ein Video ansehen, finden Sie hier eine YouTube-Version dieses Beitrags:
Übersicht
Es ist schwierig, ein responsives Navigationssystem zu erstellen. Einige Nutzer verwenden eine Tastatur, andere haben leistungsstarke Desktop-Computer und wieder andere besuchen die Website über ein kleines Mobilgerät. Alle Besucher sollten das Menü öffnen und schließen können.
Webtaktiken
Bei der Entwicklung dieser Komponente hatte ich die Gelegenheit, einige wichtige Funktionen der Webplattform zu kombinieren:
- CSS
:target - CSS-Raster
- CSS-Transformationen
- CSS-Medienabfragen für Darstellungsbereich und Nutzereinstellungen
- JS für
focusUX-Verbesserungen
Meine Lösung hat eine Seitenleiste und wird nur bei einem „mobilen“ Darstellungsbereich von 540px oder weniger umgeschaltet.
540px ist der Breakpoint für den Wechsel zwischen dem interaktiven Layout für Mobilgeräte und dem statischen Layout für Computer.
CSS-Pseudoklasse :target
Ein <a>-Link legt den URL-Hash auf #sidenav-open fest und der andere auf leer ('').
Schließlich hat ein Element die id, die mit dem Hash übereinstimmt:
<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>
Wenn Sie auf einen dieser Links klicken, ändert sich der Hash-Status der Seiten-URL. Mit einer Pseudoklasse wird dann die Sidenav ein- und ausgeblendet:
@media (max-width: 540px) {
#sidenav-open {
visibility: hidden;
}
#sidenav-open:target {
visibility: visible;
}
}
CSS-Raster
In der Vergangenheit habe ich nur Sidenav-Layouts und -Komponenten mit absoluter oder fester Position verwendet. Mit der grid-area-Syntax von Grid können wir jedoch mehrere Elemente derselben Zeile oder Spalte zuweisen.
Stapel
Das primäre Layoutelement #sidenav-container ist ein Raster, das eine Zeile und zwei Spalten erstellt, von denen jeweils eine den Namen stack hat. Wenn der Platz begrenzt ist, weist CSS alle untergeordneten Elemente des <main>-Elements demselben Raster zu. Dadurch werden alle Elemente im selben Bereich platziert und ein Stapel erstellt.
#sidenav-container {
display: grid;
grid: [stack] 1fr / min-content [stack] 1fr;
min-height: 100vh;
}
@media (max-width: 540px) {
#sidenav-container > * {
grid-area: stack;
}
}
Menühintergrund
<aside> ist das animierte Element, das die seitliche Navigation enthält. Es hat zwei untergeordnete Elemente: den Navigationscontainer <nav> mit dem Namen [nav] und einen Hintergrund <a> mit dem Namen [escape], der zum Schließen des Menüs verwendet wird.
#sidenav-open {
display: grid;
grid-template-columns: [nav] 2fr [escape] 1fr;
}
Passen Sie 2fr und 1fr an, um das gewünschte Verhältnis für das Menü-Overlay und die Schaltfläche zum Schließen im Negativraum zu finden.
CSS-3D-Transformationen und -Übergänge
Unser Layout ist jetzt bei einer mobilen Darstellungsbereichsgröße gestapelt. Bis ich neue Stile hinzufüge, wird der Artikel standardmäßig überlagert. Hier ist die UX, die ich im nächsten Abschnitt anstrebe:
- Öffnen und Schließen animieren
- Nur mit Bewegung animieren, wenn der Nutzer damit einverstanden ist
visibilityanimieren, damit der Tastaturfokus nicht auf das Element außerhalb des Bildschirms wechselt
Wenn ich mit der Implementierung von Bewegungsanimationen beginne, möchte ich die Barrierefreiheit im Blick behalten.
Bewegung für Bedienungshilfen
Nicht jeder möchte eine Slide-out-Bewegung. In unserer Lösung wird diese Einstellung angewendet, indem eine CSS-Variable --duration in einer Medienabfrage angepasst wird. Dieser Medienabfragewert stellt die Einstellung des Betriebssystems des Nutzers für Bewegungen dar (falls verfügbar).
#sidenav-open {
--duration: .6s;
}
@media (prefers-reduced-motion: reduce) {
#sidenav-open {
--duration: 1ms;
}
}
Wenn die Seitennavigation geöffnet und geschlossen wird und ein Nutzer reduzierte Bewegung bevorzugt, verschiebe ich das Element sofort in die Ansicht und behalte den Status ohne Bewegung bei.
Transition, transform, translate
Sidenav out (Standard)
Um den Standardstatus der seitlichen Navigation auf Mobilgeräten auf „offscreen“ festzulegen, positioniere ich das Element mit transform: translateX(-110vw).
Hinweis: Ich habe dem typischen Offscreen-Code von -100vw ein weiteres 10vw hinzugefügt, um sicherzustellen, dass der box-shadow der seitlichen Navigation nicht in den Haupt-Viewport hineinragt, wenn er ausgeblendet ist.
@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
Wenn das #sidenav-Element als :target übereinstimmt, setze die translateX()-Position auf die Homebase 0 und beobachte, wie CSS das Element von seiner Out-Position -110vw zu seiner In-Position 0 über var(--duration) verschiebt, wenn der URL-Hash geändert wird.
@media (max-width: 540px) {
#sidenav-open:target {
visibility: visible;
transform: translateX(0);
transition:
transform var(--duration) var(--easeOutExpo);
}
}
Sichtbarkeit des Übergangs
Das Ziel ist jetzt, das Menü vor Screenreadern zu verbergen, wenn es ausgeblendet ist, damit Systeme den Fokus nicht auf ein Menü außerhalb des Bildschirms legen. Dazu richte ich einen Sichtbarkeitsübergang ein, wenn sich der :target ändert.
- Beim Einblenden sollte die Sichtbarkeit nicht übergehen, sondern sofort sichtbar sein, damit ich sehen kann, wie das Element eingeblendet wird und den Fokus erhält.
- Beim Ausblenden wird die Sichtbarkeit verzögert, sodass sie am Ende des Übergangs zu
hiddenwechselt.
UX-Verbesserungen für Bedienungshilfen
Links
Bei dieser Lösung wird die URL geändert, um den Status zu verwalten.
Natürlich sollte hier das <a>-Element verwendet werden, das einige nützliche Bedienungshilfen bietet. Wir sollten unsere interaktiven Elemente mit Labels versehen, die ihre Funktion klar beschreiben.
<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>
Jetzt geben unsere primären Interaktionsschaltflächen sowohl für die Maus als auch für die Tastatur klar an, was sie tun.
:is(:hover, :focus)
Mit diesem praktischen funktionalen CSS-Pseudoselektor können wir unsere Hover-Stile schnell und einfach auch für den Fokus verwenden.
.hamburger:is(:hover, :focus) svg > line {
stroke: hsl(var(--brandHSL));
}
JavaScript hinzufügen
Zum Schließen escape drücken
Mit der Taste Escape auf Ihrer Tastatur sollte sich das Menü schließen, oder? Verbinden wir das.
const sidenav = document.querySelector('#sidenav-open');
sidenav.addEventListener('keyup', event => {
if (event.code === 'Escape') document.location.hash = '';
});
Browserverlauf
Damit durch das Öffnen und Schließen nicht mehrere Einträge im Browserverlauf gestapelt werden, fügen Sie dem Schließen-Button den folgenden JavaScript-Code inline hinzu:
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu" onchange="history.go(-1)"></a>
Dadurch wird der URL-Verlaufseintrag beim Schließen entfernt, so als wäre das Menü nie geöffnet worden.
Fokus-UX
Mit dem nächsten Snippet können wir den Fokus auf die Schaltflächen zum Öffnen und Schließen legen, nachdem sie geöffnet oder geschlossen wurden. Ich möchte das Umschalten vereinfachen.
sidenav.addEventListener('transitionend', e => {
const isOpen = document.location.hash === '#sidenav-open';
isOpen
? document.querySelector('#sidenav-close').focus()
: document.querySelector('#sidenav-button').focus();
})
Wenn die Seitenleiste geöffnet wird, wird der Fokus auf die Schaltfläche „Schließen“ gelegt. Wenn die Sidenav geschlossen wird,
fokussieren Sie den Button zum Öffnen. Dazu rufe ich focus() für das Element in JavaScript auf.
Fazit
Jetzt, da du weißt, wie ich es gemacht habe, wie würdest du es machen?! Das sorgt für eine interessante Komponentenarchitektur. Wer erstellt die erste Version mit Slots? 🙂
Lassen Sie uns unsere Ansätze diversifizieren und alle Möglichkeiten kennenlernen, wie wir im Web entwickeln können. Erstellen Sie einen Glitch, twittern Sie mir Ihre Version und ich füge sie dem Abschnitt Community-Remixe unten hinzu.
Community-Remixe
- @_developit mit benutzerdefinierten Elementen: Demo und Code
- @mayeedwin1 mit HTML/CSS/JS: Demo und Code
- @a_nurella mit einem Glitch-Remix: Demo und Code
- @EvroMalarkey mit HTML/CSS/JS: Demo und Code