Komponente „Sidenav“ erstellen

Grundlegende Übersicht zum Erstellen einer responsiven Slide-out-Seitennavigation

In diesem Beitrag möchte ich Ihnen zeigen, wie ich eine responsive, zustandsbehaftete Sidenav-Komponente für das Web prototypisiert habe, die die Tastaturnavigation unterstützt, mit und ohne JavaScript funktioniert und browserübergreifend eingesetzt werden kann. Demo ansehen

Wenn du lieber ein Video ansehen möchtest, findest du hier eine YouTube-Version dieses Beitrags:

Übersicht

Es ist schwierig, ein responsives Navigationssystem zu erstellen. Einige Nutzer verwenden eine Tastatur, andere einen leistungsstarken Computer und wieder andere ein kleines Mobilgerät. Jeder Besucher sollte das Menü öffnen und schließen können.

Desktop to mobile responsive layout demo
Light and dark theme down on iOS and Android

Web-Taktiken

Bei der Entwicklung dieser Komponente konnte ich einige wichtige Webplattformfunktionen kombinieren:

  1. Preisvergleichsportal :target
  2. CSS-Grid
  3. CSS-Transformationen
  4. CSS-Medienabfragen für Darstellungsbereich und Nutzereinstellungen
  5. JS für focus UX-Verbesserungen

Meine Lösung hat eine Seitenleiste und wird nur bei einem Darstellungsbereich von 540px oder weniger ein- und ausgeblendet. 540px ist der Breakpoint, an dem zwischen dem interaktiven Layout für Mobilgeräte und dem statischen Layout für Computer gewechselt wird.

CSS-Pseudoklasse :target

Mit einem <a>-Link wird der URL-Hash auf #sidenav-open und mit dem anderen auf leer ('') festgelegt. Außerdem hat ein Element die id, die dem Hash entspricht:

<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 blende ich dann die Seitenleiste ein und aus:

@media (max-width: 540px) {
  #sidenav-open {
    visibility: hidden;
  }

  #sidenav-open:target {
    visibility: visible;
  }
}

CSS-Grid

Bisher habe ich nur Sidenav-Layouts und ‑Komponenten mit absoluter oder fester Position verwendet. Mit dem grid-area-Syntax von Grid können wir jedoch mehreren Elementen dieselbe Zeile oder Spalte zuweisen.

Stapel

Das primäre Layoutelement #sidenav-container ist ein Raster mit einer Zeile und zwei Spalten, von denen jeweils eine den Namen stack hat. Wenn der Platz begrenzt ist, weist CSS allen untergeordneten Elementen des <main>-Elements denselben Grid-Namen zu. Dadurch werden alle Elemente im selben Bereich platziert und es entsteht ein Stapel.

#sidenav-container {
  display: grid;
  grid: [stack] 1fr / min-content [stack] 1fr;
  min-height: 100vh;
}

@media (max-width: 540px) {
  #sidenav-container > * {
    grid-area: stack;
  }
}

<aside> ist das animierende Element, das die Seitennavigation 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 zugehörige Schaltfläche zum Schließen zu finden.

Eine Demo, die zeigt, was passiert, wenn Sie das Verhältnis ändern.

CSS-3D-Transformationen und ‑Übergänge

Unser Layout ist jetzt bei einer Darstellungsbereichsgröße für Mobilgeräte gestapelt. Bis ich neue Stile hinzufüge, wird es standardmäßig über unserem Artikel eingeblendet. Hier sind einige UX-Aspekte, die ich im nächsten Abschnitt anstrebe:

  • Öffnen und Schließen animieren
  • Nur animieren, wenn der Nutzer damit einverstanden ist
  • Animieren Sie visibility, damit der Tastaturfokus nicht auf das Element außerhalb des Bildschirms gelangt.

Wenn ich mit der Implementierung von Bewegungsanimationen beginne, möchte ich die Barrierefreiheit von Anfang an berücksichtigen.

Barrierefreie Bewegung

Nicht jeder möchte, dass sich die Inhalte auf diese Weise einblenden. In unserer Lösung wird diese Einstellung angewendet, indem eine --duration-CSS-Variable in einer Media-Query angepasst wird. Dieser Media-Query-Wert gibt die Einstellung des Betriebssystems eines Nutzers für Bewegung an (falls verfügbar).

#sidenav-open {
  --duration: .6s;
}

@media (prefers-reduced-motion: reduce) {
  #sidenav-open {
    --duration: 1ms;
  }
}
Eine Demo der Interaktion mit und ohne angewendete Dauer.

Wenn die Seitenleiste jetzt ein- und ausgeblendet wird, wird das Element bei Nutzern, die eine reduzierte Bewegung bevorzugen, sofort in die Ansicht verschoben, wobei der Status ohne Bewegung beibehalten wird.

Übergang, Transformation, Übersetzung

Sidenav out (Standard)

Um den Standardstatus unserer Seitenleiste auf Mobilgeräten auf „Offscreen“ zu setzen, positioniere ich das Element mit transform: translateX(-110vw).

Ich habe dem typischen Offscreen-Code von -100vw ein weiteres 10vw hinzugefügt, damit das box-shadow der Seitenleiste nicht in den Haupt-Viewport ragt, wenn sie 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);
  }
}
Seitenleiste einblenden

Wenn das #sidenav-Element mit :target übereinstimmt, legen Sie die translateX()-Position auf die Homebase 0 fest. Wenn der URL-Hash geändert wird, schiebt CSS das Element über var(--duration) von seiner „Out“-Position -110vw zu seiner „In“-Position 0.

@media (max-width: 540px) {
  #sidenav-open:target {
    visibility: visible;
    transform: translateX(0);
    transition:
      transform var(--duration) var(--easeOutExpo);
  }
}

Sichtbarkeit des Übergangs

Das Ziel ist nun, das Menü für Screenreader auszublenden, wenn es nicht sichtbar ist, damit Systeme den Fokus nicht auf ein Menü legen, das sich außerhalb des Bildschirms befindet. Dazu lege ich einen Übergang für die Sichtbarkeit fest, wenn sich :target ändert.

  • Beim Einblenden soll die Sichtbarkeit nicht überblendet werden. Das Element soll sofort sichtbar sein, damit ich sehen kann, wie es eingeblendet wird und den Fokus erhält.
  • Beim Verlassen des Bildschirms sollte die Einblendung der Übergangsanimation verzögert werden, sodass sie erst am Ende der Übergangsanimation zu hidden wechselt.

UX-Verbesserungen für Bedienungshilfen

Bei dieser Lösung muss die URL geändert werden, damit der Status verwaltet werden kann. Natürlich sollte hier das <a>-Element verwendet werden, das einige nützliche Barrierefreiheitsfunktionen bietet. Wir versehen unsere interaktiven Elemente mit Labels, die die Intention klar ausdrücken.

<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>
Eine Demo der UX für Voiceover und Tastaturinteraktion.

Die primären Interaktionsschaltflächen geben jetzt sowohl für Maus als auch für Tastatur deutlich an, was sie bewirken.

: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 einfügen

Zum Schließen escape drücken

Sollte das Menü nicht durch Drücken der Taste Escape auf Ihrer Tastatur geschlossen werden? 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 gespeichert werden, fügen Sie dem Schließen-Button den folgenden Inline-JavaScript-Code 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, sodass es so aussieht, als wäre das Menü nie geöffnet worden.

Focus UX

Im nächsten Snippet wird der Fokus auf die Schaltflächen zum Öffnen und Schließen gelegt, nachdem sie geöffnet oder geschlossen wurden. Ich möchte das Umschalten einfach gestalten.

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, fokussieren Sie die Schaltfläche zum Schließen. Wenn die Seitenleiste geschlossen wird, muss der Fokus auf die Schaltfläche zum Öffnen gelegt werden. Dazu rufe ich focus() für das Element in JavaScript auf.

Fazit

Jetzt wissen Sie, wie ich es gemacht habe. Wie würden Sie es machen? Das ist eine interessante Komponentenarchitektur. Wer wird die erste Version mit Slots erstellen? 🙂

Wir möchten unsere Ansätze diversifizieren und alle Möglichkeiten kennenlernen, die das Web bietet. Erstelle einen Glitch, schreibe mir auf Twitter deine Version und ich füge sie unten im Bereich Community-Remixe hinzu.

Community-Remixe