Grid Layout/Benannte Linien und Rasterbereiche

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Bei jedem Raster stellt sich die Frage nach der optimalen Anzahl der Zeilen und Spalten, und wie man die einzelnen Rasterelemente identifiziert. Im Schach (oder beim Schiffe Versenken Spiel) verwendet man Buchstaben und Zahlen für Zeilen und Spalten. Im Printbereich hat sich ein 12er-Raster (wie bei bootstrap) bewährt, da es durch 2,3,4,6 teilbar ist und so viele verschiedene Kombinationen erlaubt.

Im WebDesign geht es eben aber nicht um ein Design für eine bestimmte Größe mit einem bestimten Seitenverhältnis, sondern um ein Design für eine Vielzahl von Viewports. Je nach Viewportgröße kann es sinnvoll sein, Inhaltsbereiche anders anzuordnen – mal untereinander, mal nebeneinander. Verwendet man dafür Grid-Liniennummern, verliert man sehr schnell den Überblick.

Das Grid Layoutsystem bietet deshalb zwei Möglichkeiten an, Linien und Bereiche im Grid mit Namen zu versehen. Im besten Fall ist es möglich, mit Hilfe der grid-template-areas Eigenschaft das Raster mit seinen Bereichen als ASCII-Schema zu beschreiben und dieses Schema mit Hilfe von @media- oder @container-Abfragen dynamisch an die Größe von Viewport oder Container anzupassen.

Während Responsive Raster ohne Media Queries vor allem den auto-placement-Algorithmus vorstellte, zeigt dieses Tutorial, wie man feste Layouts mit wenig Aufwand im CSS festlegen und formatieren kann.

Benannte Rasterbereiche

Wir wollen mit dem einfachsten Fall beginnen: Ein Gitter besteht aus einigen Bereichen, die sich nicht überlappen und die den Gitterbereich vollständig ausfüllen. Zum Beispiel so, wie das folgende Bild zeigt:

Idee für ein Seitenlayout

„Zeichnen“ eines Layouts

Um das in CSS zu übersetzen, muss man sich für die Bereiche kurze und inhaltlich passende Namen ausdenken und diese so aufschreiben, dass der Browser daraus ablesen kann, welcher Bereich wo beginnt und wo endet. Dafür notiert man für jede Gitterzeile einen String, der so viele Bereichsnamen enthält, wie das Grid Spalten haben soll. Überdeckt ein Bereich mehrere Spalten, notiert man seinen Namen für jede Spalte, die er belegt, einmal. Überdeckt ein Bereich mehrere Zeilen, notiert man seinen Namen in mehreren Zeilen.

Wichtig ist dabei nur, dass die so „gemalten“ Bereiche Vierecke bilden.

Bitte wundern Sie sich nicht, dass der main-Bereich über zwei Grid-Spalten geht. Man hätte auf die senkrechte Grid-Linie 3 verzichten und dafür 2fr als Spaltenbreite setzen können. Aber wir werden diese Grid-Linie später noch verwenden.

Grundraster mit festgelegten Rasterbereichen ansehen …
body {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-areas: 
    "head head head head"
    "nav  main main news"
    "nav  main main links" 
    "foot foot foot foot";
}

Die gezeigte CSS-Regel legt das Grundlayout einer Webseite mit Hilfe von benannten Bereichen fest. Sie können sich das wie einen Wireframe-Entwurf oder eine ASCII-Zeichung vorstellen.

Zum einen wird durch grid-template-areas festgelegt, wo sich die Bereiche befinden:

  • der head-Bereich befindet sich in Zeile 1 und erstreckt sich über alle vier Spalten
  • nav nimmt in der linke Spalte die Zeilen 2 und 3 ein
  • main ist der große zentrale Bereich in der Mitte (2 Spalten breit, 2 Zeilen hoch)
  • news und links sitzen rechts daneben, in Zeile 2 und 3.
  • Darunter befindet sich der foot-Bereich, der wieder über alle Spalten geht.

Die Spaltenbreiten werden davon getrennt mit grid-template-columns: repeat(4, 1fr); festgelegt, diese Angabe unterteilt das Raster in 4 gleich breite Spalten. Die repeat()-Funktion kennen wir bereits aus dem vorigen Kapitel. Die Zeilenhöhen sind nicht angegeben, die Grid-Zeilen werden damit nach Bedarf automatisch erzeugt und sind so hoch wie die darin platzierten Elemente.

Auf diese Weise zeichnen wir das Grundlayout der Webseite direkt im CSS.

Das bedeutet:

  • Struktur wird sichtbar im Code
  • Positionierung erfolgt über Namen statt Zahlen
  • das Layout ist sofort als „Bild“ lesbar

Die Namen bewirken für sich erst einmal noch nichts. Sie liefern uns aber ein Gerüst, in das wir unsere HTML-Elemente einhängen können. Dazu verwenden wir wieder die grid-area-Eigenschaft, verwenden nun aber die Variante mit nur einem Wert. Findet der Browser bei grid-area einen Bereichsnamen statt einer Zahl vor, lässt er das Element an den Grenzen dieses Bereichs beginnen und enden. Wie diese Zuordnung genau geschieht, werden wir beschreiben, wenn es um benannte Rasterlinien geht.

header { grid-area: head; }
nav    { grid-area: nav; }
main   { grid-area: main; }
#links { grid-area: links; }
#news  { grid-area: news; }

Die beiden aside-Boxen müssen anhand ihrer Ids angesprochen werden, um den Bereich eindeutig zuordnen zu können.

Auch im Seiteninspektor (hier Firefox 151) können die Rasterbereiche angezeigt werden:

Screenshot eines Grids im Firefox 151

In Chromium-Browsern muss man zunächst auf der Elementansicht das grid-Badge des Grid-Containers anklicken, damit die Rasterbereiche angezeigt werden, und dann im rechten Info-Bereich das Layout-Tab anzeigen. Dort kann man die Anzeige der Bereichsnamen aktivieren.

Anpassungen an andere Viewports

Mit dieser Methode können Anpassungen an andere Viewports sehr schnell vorgenommen werden:

responsive Raster mit Media Queries ansehen …
body {
	display: grid;
	gap: 0.5em;
    /* Standard: alles in eine Spalte */
	grid-template-areas:
	  "head"
	  "main"
	  "nav"
	  "news"
	  "links"
	  "foot";
}
@media (min-width: 35em) { 
	body {
        /* Etwas mehr Platz: 3 Spalten, news und links */
		grid-template-columns: repeat(3, 1fr);
		grid-template-areas: 
			"head head head "
			"nav  news links"
			"main main main" 
			"foot foot foot";		
	}
}
@media (min-width: 45em) { 
	body {
        /* Reichlich Platz: 4 Spalten, news und links rechts neben main */
		grid-template-columns: repeat(4, 1fr);
		grid-template-areas: 
			"head head head head" 
			"nav  main main news" 
			"nav  main main links" 
			"foot foot foot foot";
	}
}

Standardmäßig (Mobile First!) werde alle Elemente untereinander dargestellt – allerdings wird die Navigation nach unten geschoben, damit der Hauptinhalt sicher im Viewport ist.

Bei einer Viewportgröße von mindestens 35em greift die erste @media-Query, erzeugt 3 gleichbreite Spalten und schaltet die entsprechenden Werte für grid-template-areas um, sodass die Navigation und die Aside-Boxen nebeneinander dargestellt werden. Der Hauptinhalt kommt darunter.

Ab 45em Breite werden vier Spalten und das „normale“ Desktop-Layout erzeugt.

Leerstellen

Manchmal hat man aber auch das Problem, dass für einen Bereich im Grid kein Inhalt verfügbar ist. Oder man braucht dafür einfach keinen Namen. Solche anonyme Zellen werden durch einen Punkt markiert.

Leere Rasterbereiche ansehen …
body {
	display: grid;
	gap: 0.5em;
	grid-template-columns: 1fr 2fr 1fr;
	grid-template-areas:
		"head head head"
		".    main nav"
		".    main news"
		"foot foot foot";
	grid-template-rows: 
		min-content 
		[nav-start main-top] 1fr 
		[aside-start] 1fr
		[footer-start main-bottom] min-content;				
}

Der Header (head) und der Footer (foot) erstrecken sich jeweils über die gesamte Breite des Rasters.

Der Hauptinhalt (main) belegt in der Mitte einen Bereich über zwei Zeilen. Rechts daneben befinden sich die Bereiche nav und news.

Die Punkte (.) links kennzeichnen leere Rasterzellen, denen kein Rasterbereich zugeordnet ist. Sie bilden Weißraum, der den Focus auf den Inhalt in der Mitte legt.

Kurzfassung: grid-template

Es ist unübersichtlich, Bereichsnamen und Bereichsgrößen voneinander getrennt aufzuschreiben. Deshalb gibt es die Shorthand-Eigenschaft grid-template, mit deren zweiter Syntax sich Layoutzeichnung und -bemaßung kombinieren lassen. grid-template erweitert dafür die grid-template-areas-Syntax. Dort haben wir für jede explizite Grid-Zeile einen String, der die Bereichsnamen definiert. Um die Zeilenhöhen anzugeben, notieren wir sie einfach hinter dem entsprechenden String. Der Wert für grid-template-columns wird, abgetrennt durch einen Schrägstrich, einfach dahinter notiert:

Kurzschreibweise mit grid-template
	grid-template: 
		"head head head head"  auto
		"nav  main main news"  1fr
		"nav  main main links" 1fr
		"foot foot foot foot"  auto
       / 1fr  1fr  1fr  1fr;

Auf diese Weise erhält man eine hübsche Übersichtstabelle für die verwendeten Abmessungen.

Beachte: Die repeat()-Funktion kann in dieser Schreibweise nicht genutzt werden!
Beachte: Es gibt eine noch stärker verkürzende Eigenschaft namens grid. Sie versteht zum einen die grid-template Syntax, hat aber auch eine andere Syntax zur Angabe von Zeilen- oder Spaltentemplate zusammen mit einer Autoflow-Festlegung für die jeweils andere Grid-Achse. Wenn Du grid mit der Syntax von grid-template verwendest, werden die grid-auto-*-Eigenschaften auf ihre Standardwerte zurückgesetzt!

Benannte Linien

Benannte Bereiche mit grid-template-areas sind eine elegante Möglichkeit, ein Layout zu beschreiben. Sie hat allerdings den Nachteil, dass sich diese Bereiche nicht überlappen dürfen. So etwas kann zum Beispiel interessant sein, wenn die Navigation auf schmalen Viewports zu einem Button kollabiert und das Navigationsmenü so geöffnet werden soll, dass es einen Teil des main-Bereichs überdeckt. Das ist aber etwas, was wir dem Leser zur Übung überlassen wollen.

Implizit erzeugte Liniennamen

Es ist so, dass ein benannter Bereich, der mit grid-template-areas eingeführt wird, für den Browser gar nicht existiert. Statt dessen weist er den Rasterlinien, die den Bereich begrenzen, Namen zu. Wenn beispielsweise der main-Bereich die Grid-Zeilen 2 bis 4 und die Grid-Spalten 2 bis 3 belegt, dann wird er durch die senkrechten Rasterlinien 2 und 4 und durch die horizontalen Rasterlinien 2 und 5 begrenzt. Der Browser ordnet darum diesen Rasterlinien Namen zu:

Liniennamen zu Gridbereichen

Wie man sieht, kann eine Rasterlinie auch mehr als einen Namen tragen, und die Namen für horizontale und vertikale Linien müssen nicht verschieden sein. Der Browser weiß aus den Kontext, ob mit einem Namen eine vertikale oder eine horizontale Linie gemeint ist.

Wenn wir, wie im Abschnitt über benannte Bereiche, eine Angabe wie grid-area: main; machen, dann interpretiert der Browser das zunächst als

 grid-area: main / main / main / main;

Um besser mit grid-template-areas zusammenzuarbeiten, sucht der Browser bei Antreffen eines Namens zunächst nicht nach main, sondern für start-Angaben (grid-row-start, grid-column-start) nach main-start und für end-Angaben nach main-end, so dass schlussendlich die folgenden Werte benutzt werden (aufgeführt in der Reihenfolge, wie sie bei grid-area anzugeben sind).

 grid-row-start: main-start;
 grid-column-start: main-start;
 grid-row-end: main-end;
 grid-column-end: main-end;

Nur dann, wenn er den um -start oder -end erweiterten Namen nicht findet, sucht der Browser nach einer Rasterlinie, die exakt den angegebenen Namen trägt.

Linien explizit benennen

Außer durch grid-template-areas können Gridlinien auch explizit durch grid-template-rows und grid-template-columns benannt werden. Um die Syntax besser verstehen zu können, möchten wir daran erinnern, dass die grid-template-* Eigenschaften vor allem die Zeilenhöhen und Spaltenbreiten beschreiben, also das, was sich zwischen den Rasterlinien befindet. Stellen wir uns vor, in der CSS-Eigenschaft würde die Stelle, wo sich die Rasterlinien befinden, durch eckige Klammern symbolisiert:

 grid-template-columns: [links] 1fr [inhalt-start] 3fr [inhalt-end] 1fr [rechts];

Das wäre ein Grid mit 3 Spalten, die durch vier Rasterlinien abgegrenzt werden. Und tatsächlich ist die Syntax genau so: Man schreibt vor, zwischen oder nach den Breitenangaben ein paar eckige Klammern und dort hinein die Namen, die man für diese Rasterlinie vergeben möchte, durch Leerzeichen getrennt.

Um in diesem Grid ein Element über alle drei Spalte zu platzieren, kann man

 grid-column: links / rechts;

schreiben.

Um es in der mittleren Spalte zu platzieren, würde man

 grid-column: inhalt-start / inhalt-end;

notieren. Dabei haben die grid-row- und grid-column-Eigenschaften eine besondere Fähigkeit: Ist die Start-Angabe ein Name und die Ende-Angabe fehlt, dann wird die Start-Angabe auch für die Ende-Angabe verwendet. Zusammen mit der automatischen Ergänzung von -start und -end für Liniennamen bewirkt das, dass man auch einfach

 grid-column: inhalt;

schreiben kann.

Oder, wenn das Element in Zeile 3 sein soll und man grid-area verwenden möchte:

 grid-area: 3 / inhalt-start / 4 / inhalt-end;

Auch hier gibt es wieder Verkürzungen: Lässt man den vierten Wert (Linie für das Spaltenende) weg und ist der zweite Wert (Linie für den Spaltenanfang) ein Name, übernimmt der Browser den Startwert für das Spaltenende. Man kann die Schreibweise also zu

  grid-area: 3 / inhalt / 4;

verkürzen.

Benannte Linien in grid-template und Overlay-Bereiche

Benannte Linien sind auch mit der grid-template-Eigenschaft möglich. Hier gibt es zwei Varianten zu beachten

Liniennamen können – in eckigen Klammern – vor oder hinter einer Zeilendefinition notiert werden, um horizontale Linien zu benennen. Zur Erinnerung: eine Zeilendefinition in grid-template besteht aus dem String mit den Spaltennamen und der optionalen Höhenangabe für diese Zeile.

Benannte Linien mit grid-template
	grid-template: 
						"head head head head"  auto
		[content-top]	"nav  main main news"  1fr
						"nav  main main links" 1fr [content-bottom]
						"foot foot foot foot"  auto
				       / 1fr  1fr [main-center] 1fr  1fr [right];

Dieses Beispiel fügt dem Grid Namen für die horizontalen Linien 2 und 4 und für die vertikale Linie 3 hinzu. Wollte man das Layout mit einem Element überlagern, dass die rechte Hälfte des main-Bereichs und die beiden asides (news und links) überdeckt, dann könnte man es mit

  grid-area: content-top / main-center / content-bottom / right;

platzieren. Allerdings wäre das nicht sehr clever und vor allem drückt es nicht aus, was man damit vorhat. Besser ist es, content-top und main-center in overlay-start umzubenennen, sowie content-bottom und right in overlay-end. Damit erhält man einen überlappenden Grid-Bereich so, als ob er von grid-template-areas erstellt worden wäre, und kann diesen Namen einfach bei grid-area einsetzen:

Überlappende Bereiche mit grid-template
body {
	grid-template: 
						"head head head head"  auto
		[overlay-start]	"nav  main main news"  1fr
						"nav  main main links" 1fr [overlay-end]
						"foot foot foot foot"  auto
				       / 1fr  1fr [overlay-start] 1fr  1fr [overlay-end];
}

#overlay {
   grid-area: overlay;
}

Natürlich müssen Sie nicht zwingend das grid-template Shorthand verwenden, um Overlay-Bereiche zu definieren. Wir haben das lediglich hier als Beispiel verwendet; Sie können die Liniennamen auch mit grid-template-rows und grid-template-columns definieren.

Hinweis:
Bei grid-template-rows und grid-template-columns darf man zwischen zwei Höhenangaben immer nur eine Klammergruppe setzen. Die grid-template Syntax lockert das etwas: man kann hinter eine Zeile und vor die nächste Zeile eine Klammergruppe setzen. Dadurch wird besser ausgedrückt, ob man sich den Namen vor einer Zeile oder hinter eine Zeile vorstellt. Technisch wird immer die Gridlinie zwischen den Zeilen benannt.

Mehrere Rasterlinien mit dem gleichen Namen

Dass eine Rasterlinie mehrere Namen haben kann, wurde schon gezeigt. Dass der gleiche Name für horizontale und vertikale Rasterlinien verwendet werden kann, hatten wir auch schon.

Aber es ist auch möglich, dass der gleiche Name auf einer Achse mehrfach verwendet wird. Über eine grid-template-areas-Definition gelingt das nicht, wenn man aber auf die areas-Syntax verzichtet und alle Liniennamen mit grid-template-rows und grid-template-columns erstellt, hat man freie Hand. Um es noch schlimmer zu machen: Innerhalb der repeat()-Funktion können ebenfalls Liniennamen erzeugt werden.

benannte Linien mit mehreren Namen
body {
	display: grid;
	grid-template-columns: 
		[nav-start]         10em
		repeat(3, [content] 1fr)
  	grid-template-rows: auto;
}
section:nth-of-type(1) {
   grid-column: content 1 / span 1;
}
section:nth-of-type(2) {
   grid-column: content 2 / span 1;
}
section:nth-of-type(3) {
   grid-column: content 3 / span 1;
}

Die Lösung ist, dass die Namens-Syntax um die laufende Nummer des Namens erweitert werden kann. Im Beispiel bekommen die vertikalen Rasterlinien 2, 3 und 4 den Namen content zugewiesen. Welche gemeint ist, ergibt sich aus der Nummer. Diese Nummer muss ganzzahlig und größer als 0 sein.

Beachte: Sie kann auch größer sein als die Anzahl der Rasterlinien mit diesem Namen, in diesem Fall tut der Browser so, als hätten alle implizit erzeugten Rasterlinien ebenfalls diesen Namen bekommen. Diese Formulierung kann auf Glatteis führen. Im obigen Beispiel haben die Rasterlinien 2, 3 und 4 den Namen content. Wenn ich für eine Section content 4 angebe, dann könnte man erwarten, dass das Grid um implizite Spalten erweitert wird und die Section in der Grid-Spalte 5 erscheint. Das ist aber nicht der Fall, sie erscheint in Grid-Spalte sechs. Zum expliziten Grid gehört nämlich auch noch die Rasterlinie 5, rechts von der content-Spalte 3, und die erste implizite Rasterlinie ist die Nummer 6, wodurch eine leere Spalte entsteht. Um diesen Sprung zu vermeiden, muss man hinter der repeat()-Gruppe ein weiteres Mal [content] angeben, damit die vertikale Rasterlinie 5 nicht namenlos ist.

Anwendungsbeispiele

Navigation als Kreuz

Im CSS-Einstieg gibt es ein Beispiel, in dem als Dekoration ein Querbalken (engl. rule) eingefügt werden soll:


Grid Layout mit Pseudoelement zur Dekoration ansehen …
:root {
	--navWidth: 12em;
	--mainWidth: min(48em, calc(100% - var(--navWidth)));
	--headerLineHeight: 3em;
}

@media (width > 30em) {
  body { 
    display: grid;  
    grid-template:	". nav head ." auto
					". nav rule ." var(--headerLineHeight)
                    ". nav main ." auto
                    ". nav foot ." auto /
					1fr var(--navWidth) var(--mainWidth) 1fr;
    } 
    body::before {
		content: '';
		grid-row: rule;
		grid-column: 1 / 1;
		background: linear-gradient(to right, var(--lila2), var(--lila1));
    }
    body::after {
		content: '';
		grid-row: rule;
		grid-column: rule-start / -1;
		background-image: linear-gradient(to right, var(--lila), var(--lila-darker));
    }
}

Dieser Querbalken soll aber erst auf größeren Viewports sichtbar werden.

Im :root-Selektor werden die entsprechenden Werte schon in custom properties notiert. Ab einer Breite von 30em erhält der body zwei Pseudoelemente, die im Grid entsprechend platziert werden.

Vom Entwurf zum Layout

Formular

Formulare müssen klar und übersichtlich sein. Häufig wurde die Gestaltung durch zahlreiche wrapper-Elemente und Zeilenumbrüche erreicht. Mit dem Grid Layout können wir nun alle Formular-Elemente an einem Raster ausrichten:

Formular ansehen …
form {
  display: grid;
  grid-template-columns: [labels] auto [controls] auto [oversized] auto;
  grid-auto-flow: row dense;
}
form > label {
  grid-column: labels;
  grid-row: auto;
}
form > input, form > select {
  grid-column: controls;
  grid-row: auto;
}
#department-block {
  grid-row: span 3;
  grid-column: oversized;
}

#buttons {
  grid-row: auto;
  grid-column: 1 / -1;
  text-align: end;
}

Das Formular form wird zu einem Raster mit drei Spalten, deren Größe inhaltsbasiert ist. Die entsprechende Rasterlinien erhalten aussagekräftige Namen.

Alle label-Elemente werden in die label-Spalte platziert; mit grid-row: auto; werden sie automatisch auf die Zeilen verteilt.

Analog wird mit den Eingabeelementen verfahren, die in der controls-Spalte platziert werden.

Das select-Menü mit der id department-block wird in die oversized-Spalte positioniert und umspannt dort 3 Zeilen (Auf mobilen Geräten wird das select-Menü durch ein Popup mit radio-Buttons ersetzt.)

Die Buttons werden von einem div umschlossen, das sich wegen grid-column: 1 / -1; über die gesamte Breite des Rasters erstreckt.


Periodensystem der Elemente

Die chemischen Elemente sind in einer Liste, die im Periodensystem nach steigender Kernladungszahl angeordnet sind. Dabei gibt es je nach Anzahl der Elektronen auf der Schale bestimmte Abstände.

Dies hat Gunnar Bittersmann in einem Pen mit grid-row und grid-column positioniert. Die Zahlen innerhalb der nth-of-type-Selektoren entsprechen der jeweiligen Ordnungszahl des Elements.

Anordnung und Lücken im Periodensystem
#periodic-table li:nth-of-type(2)
{
	grid-column: 18;
}

#periodic-table li:nth-of-type(5),
#periodic-table li:nth-of-type(13)
{
	grid-column: 13;
}

#periodic-table li:nth-of-type(n+58):nth-of-type(-n+71)
{
	grid-row: lanthanides;
}

#periodic-table li:nth-of-type(n+90):nth-of-type(-n+103)
{
	grid-row: actinides;
}

Fazit

Mit den benannten Linien und mehr noch, mit den ASCII-Rastern kann man das Layout einer Webseite mit wenigen Zeilen CSS festlegen und über Media Queries entsprechend aktivieren. Allerdings müssen bei komplexeren Layouts oder anders aufgebauten Unterseiten neue Raster angelegt werden.


Weblinks