page in English

Das gerasterte Menü oder: Die Stärke von CSS

Nils Hörrmann

In den vergangenen Jahren haben wir verschiedene Webprojekte realisiert, die spezielle Formulargestaltung benötigten, darunter benutzerdefinierter Comboboxen und Auswahlfelder. Auch wenn wir versucht haben, bestehende Stile und Skripte wiederzuverwenden, haben sich die Dinge aufgrund unterschiedlicher Projektanforderungen im Laufe der Zeit auseinander entwickelt. Letztes Jahr haben wir begonnen, unsere Komponenten wieder zusammenzufassen und haben dies genutzt, um auch unseren Gestaltungsansatz zu überdenken. In diesem Zusammenhang ist uns klar geworden, wie sehr es sich lohnt, auf modernes CSS zu vertrauen.

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.

Offenes Menü, das neben der Schaltfläche positioniert ist und die Rasterzellen auf die Menügröße erweitert. Screenshot.
Abbildung Wenn alle Zellen auf min-content gesetzt sind, berechnet der Browser Höhe und Breite beim Öffnen jedes Mal neu, was zu einem “Springen” des gesamten Layouts führt.

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;
}
Offenes Menü das neben der Schaltfläche schwebt mit eingeblendeten Rasterlinien. Screenshot.
Abbildung Indem wir die Zellen auf Null setzen, kann das Menü über den umstehenden Inhalten schweben und die Schaltfläche gleichzeitig zu jedem Zeitpunkt ihre Größe beibehalten.

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.

Weiterführende Links