Ich gestalte seit 1999 Websites, weshalb meine ersten Designs vor allem Layouttabellen und Spacer-Gifs enthielten. Im Laufe der Jahre wurde CSS wichtiger und funktionsreicher, aber oft war es immer noch hacky, interessante Layouts zu erstellen. Wir haben Floats zur Positionierung verwendet, die ursprünglich für etwas anderes gedacht waren, wir haben alle möglichen kreativen Lösungen entwickelt. Aber komplexere Layouts erforderten stets JavaScript für Positionierung, Timing, Animation.
Die Dinge änderten sich grundlegend mit der Einführung von CSS Flexbox und CSS Grid, die uns endlich die Freiheit gaben, im Raum zu entwerfen. Kinners, wie ich CSS Grid liebe! Aber während der Arbeit an unseren Formularkomponenten fiel mir auf, wie schwer es sein kann, die gewohnten Pfade zu verlassen und nicht in alte Denkmuster zu verfallen:
Ich wollte eine Menükomponente erstellen, die sich durch Klicken auf einen Button in eine der vier Himmelsrichtungen öffnet und das Menü dabei am Anfang, in der Mitte oder am Ende der zugehörigen Achse ausrichtet. Ich dachte zunächst, dass die Positionierung des Menüs komplizierte Berechnungen mit sich bringen würde. Ein Blick auf bestehende Bibliotheken schien dies zu bestätigen, da sich diese stets auf umfangreiches JavaScript mit absoluter Positionierung und komplexen Entfernungsberechnungen stützten.
Das fühlte sich falsch an, denn eine der Stärken von CSS Flexbox und Grid ist die Fähigkeit, komplexe Berechnungen für uns durchzuführen. Sie ermöglichen eine einfache Ausrichtung (selbst Zentrierungen sind leicht, meine alten Web-Veteranen!), also genau das, was ich für mein Menü brauchte. Mit etwas Nachdenken fand ich eine überraschend einfache Lösung für mein Problem: Zellen ohne Breite und Höhe in Kombination mit intelligenter Overflow-Handhabung.
Das unsichtbare Raster
Um das Menü überall dort zu positionieren, wo ich wollte, brauchte ich ein Drei-mal-drei-Raster mit der Schaltfläche in der Mitte und dem Menü in einer der umlaufenden Zellen, abhängig von der erforderlichen Platzierung und Ausrichtung:
.wrapper {
grid-template-columns: min-content min-content min-content;
grid-template-rows: min-content min-content min-content;
}
button {
grid-column-start: 2;
grid-row-start: 2;
}
menu {
/* unten platziert, nach links ausgerichtet */
grid-column-start: 2;
grid-row-start: 3;
justify-self: start;
align-self: start;
}
Während dieser Aufbau gut funktioniert, wenn das Menü geschlossen ist, beginnen sich die Zellen, die das Menü halten, auf die Größe des Menüs zu erweitern, wenn es geöffnet wird, und das gesamte Seitenlayout beginnt dadurch zu springen. Eigentlich sollte das Menü jedoch über den umliegenden Inhalten schweben, was für gewöhnlich mittels absoluter Positionierung gelöst wird und skriptbasierte Lageberechnungen erfordert.
Das Tolle an CSS Grid ist: Es erlaubt überlaufenden Inhalt, wenn die Größe der äußeren Zellen auf 0
gesetzt wird. Dadurch schwebt das Menü – ganz ohne Zutun – über dem umliegenden Inhalt:
.wrapper {
grid-template-columns: 0 min-content 0;
grid-template-rows: 0 min-content 0;
}
button {
grid-column-start: 2;
grid-row-start: 2;
}
menu {
/* unten platziert, nach links ausgerichtet */
grid-column-start: 2;
grid-row-start: 3;
justify-self: start;
align-self: start;
}
Der Verzicht auf absolute Positionierung und das Vertrauen auf die automatische Ausrichtung mittels CSS eröffnet eine zusätzliche Möglichkeit: das Anwenden von position: sticky;
auf das Menü. Dadurch können wir sicherstellen, dass es beim Scrollen nicht ungewollt den sichtbaren Fensterbereich verlässt, bevor nicht auch der Button hinausgescrollt wird.
menu {
/* unten platziert, nach links ausgerichtet */
grid-column-start: 2;
grid-row-start: 3;
justify-self: start;
align-self: start;
/* stick zum nächsten scrollbaren Elternteil */
position: sticky;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
Die Schaltfläche, das Menü und die Geisterzellen
Dieser Ansatz funktionierte wunderbar für die Zellen, die sich keinen Platz mit der Schaltfläche teilen, und ich war sehr zufrieden, bis ich bemerkte, dass die Seite beim Öffnen des Menüs in der Mitte der Schaltfläche zu springen begann. Während die äußeren Rasterzellen leicht auf Null schrumpfen konnten, weil sie nicht mit einem anderen Element konkurrierten, waren die mittleren Zellen immer noch auf min-content
eingestellt und standen daher im Konflikt mit der Buttongröße. Der Browser berechnete die Zellgrößen beim Öffnen des Menüs neu, wodurch es dann nicht mehr über dem Seiteninhalt schwebte.
Dieses Problem schien unlösbar, bis ich bemerkte, dass es eine Möglichkeit gab, min-content
und 0
Breite gleichzeitig auf die Schaltfläche anzuwenden, indem man sie in mehrere Zellen unterteilt:
.wrapper {
grid-template-columns: 0 min-content 0 min-content 0;
grid-template-rows: 0 min-content 0 min-content 0;
}
button {
grid-column: 2 / -2;
grid-row: 2 / -2;
align-self: center;
justify-self: center;
}
Jetzt haben wir ein Fünf-mal-fünf-Raster mit minimierten Zellen, die für das Menü reserviert sind, und einer Schaltfläche, die sich zentriert über alle innenliegenden Geisterzellen hinweg erstreckt.
Demo
http://labor.hananils.de/menu/index.htm
Natürlich ist dies nicht alles, was für ein funktionierendes Menü benötigt wird. Es ist JavaScript erforderlich, um das Menü auf- und zuzuklappen und sicherzustellen, dass alles zugänglich ist, einschließlich der Tastaturnavigation. Aber es ist ein gutes Beispiel dafür, wie moderne CSS-Techniken uns helfen können, komplexe Layout-Aufgaben zu lösen, ohne die Dinge unnötig zu verkomplizieren. Und auch dafür, bekannte Pfade bei der Umsetzung von Layouts regelmäßig zu hinterfragen und zu überdenken.