Mobile Navigationsmenüs ohne JavaScript
Um unsere Firmen Webseite möglichst barrierefrei zu gestalten, wollten wir zentrale Funktionen ohne JavaScript verfügbar machen. Aus diesem Grund beschäftigten wir uns mit der Frage ob ein mobiles Navigationsmenü durch reines HTML und CSS gelöst werden kann.
Die zentrale Umsetzung
Der vollständige Code ist als Gist auf GitHub zu finden.
Auf HTML Seite halten wir die Navigation recht einfach. Im <nav> Bereich sind zwei Listen, eine davon ist für die mobile Ansicht mit einem ausklappbaren details-Block umschlossen:
<nav>
<!-- desktop -->
<ul>
<li><a href="/">About</a></li>
<li><a href="/">Blog</a></li>
<li><a href="/">Contact</a></li>
</ul>
<!-- mobile -->
<details>
<summary aria-label="open mobile menu"></summary>
<ul>
<li><a href="/">About</a></li>
<li><a href="/">Blog</a></li>
<li><a href="/">Contact</a></li>
</ul>
</details>
</nav>
Auf dem Desktop wollen wir den details
-Tag und dessen Inhalte nicht sehen, in der mobilen Ansicht wiederum das Desktop-Menü nicht:
nav>details {
display: none;
}
@media (max-width: 991px) {
/* hide desktop menu */
nav>ul {
display: none !important;
}
/* show mobile menu */
nav>details {
display: block !important;
}
}
Fertig?!
Unsere Lösung erfüllt nun alle technischen Anforderungen an ein responsives Menü. Visuell möchten wir im folgenden Teil das Ergebnis noch verbessern.
Ein Hamburger Menü Icon hinzufügen
Meistens sehen wir ein Hamburger Menü Icon und ein Schließen-Icon in mobilen Varianten von Webseiten. Das soll hier natürlich nicht fehlen – und nur mit CSS umgesetzt werden:
/* custom image for mobile menu */
nav > details > summary::after {
content: '';
background-image: url(img/menu.svg);
background-size: 2rem 2rem;
display: inline-block;
width: 2rem;
height: 2rem;
}
/* alternative image for open mobile menu */
nav > details[open] > summary::after {
background-image: url(img/close.svg);
}
/* icons used: https://ionic.io/ionicons */
Das details[open]
-Attribut lässt uns prüfen, ob <details>
geöffnet oder geschlossen ist. Wenn es offen ist, ersetzen wir in unserem Fall das Hintergrundbild.
Da wir mit dem ::after
Pseudoelement arbeiten, wollen wir dafür sorgen, dass das Icon nicht die ganze Seite einnimmt. Mit background-size
, display: inline-block;
, width: 2rem;
und height: 2rem;
in Kombination können wir dafür sorgen, dass das Icon unsere gewünschte Größe hat.
Animationen
Da wir mit CSS nicht die Eltern-Knoten auswählen können, greifen wir hier zu einem kleinen Trick, der eine dezente Animation hinzufügt. Animationen sind nicht zwingend notwendig, da es sich hier, meiner Meinung nach, nicht um eine offensichtliche Lösung handelt, wollen wir uns kurz anschauen, was möglich ist:
nav>details>summary {
[...]
/* slight transition */
transition: margin-bottom 150ms ease-out;
}
/* add margin for slight transition */
nav>details[open]>summary {
margin-bottom: 1rem;
}
Da wir mit CSS Selektoren (noch?) nicht auf ein Eltern-Element zugreifen können um dieses im Ganzen zu animieren, fügen wir im offenen Zustand des dialog
einen kleinen margin-bottom
hinzu, der mit dem transistion
-Attribut von 0rem
auf 1rem
in 150ms
wächst.
Das Layout anpassen
Das genaue Layout unterliegt individuellen Anforderungen. Daher soll in diesem Artikel nicht weiter darauf eingegangen werden. Nichtsdestotrotz haben wir im Gist den Code für eine ordentliche Layout Variante als Beispiel angereichert.
Visuelle Variationen
Ein Fixierter Header
Der bestehende Code lässt die Navigationsleiste mit der gesamten Webseite scrollen. Falls diese jedoch immer sichtbar sein soll, können wir sie wie folgt fixieren:
header {
background-color: aliceblue;
display: flex;
justify-content: space-between;
/** toggle fixed header **/
position: fixed;
width: 100%;
}
main {
/* don't forget to increase the top padding with fixed navbar */
padding: 4rem 1rem;
}
Ein bildschirmfüllender, fixierter Header
Bis jetzt ist der header
im aufgeklappten Zustand nur so groß wie der Inhalt. Falls das nicht gewünscht ist und er stattdessen die ganze Seite ausfüllen soll, können wir das wie folgt beeinflussen:
nav>details>ul {
flex-direction: column;
align-items: end;
margin: 0;
/** toggle full page menu **/
height: 100vh;
}
Ein bekanntes Problem
Durch fehlendes, sogenanntes "Focus-Trapping" kann die Seite im Hintergrund weiter gescrollt werden. Das kann ausschließlich durch JavaScript verhindert werden (Stand: 2023). Daher haben wir uns bei unserer Webseite für den fixierten Header entschieden, der nicht bildschirmfüllend ist.