Über subgrid und farbige Rasterlinien
Für mich war das subgrid immer das, was mir fehlte, um grid wirklich in meine workflows zu integrieren.
Mit CSS grid können “row” und “column tracks”, die in einem übergeordneten Raster (grid
) erstellt wurden, nur für die Positionierung der direkten Kinder des Raster-Containers verwendet werden. Subgrid ermöglicht die gemeinsame Nutzung aller im übergeordneten grid festgelegten Werte.
Anstatt die “line names” und “track sizing functions” explizit zu definieren, verwenden wir das Schlüsselwort subgrid
als Wert für grid-template-columns
oder grid-template-rows
, um den “grid track” vom nächstgelegenen grid zu erben.
Zum Beispiel könnte ein klassisches 12-Spalten-Raster übergeordnet für die gesamte Seite erstellt werden, und wir können dann unsere Elemente darin anordnen, egal wie tief sie verschachtelt sind. Du kannst die derzeitige Browser-Unterstützung für CSS Subgrid auf caniuse.com nachsehen.
Inhaltsverzeichnis überspringen
Inhaltsverzeichnis
Vorbereitung
Für ein neues Projekt liegt mir ein Entwurf vor, der für Subgrid wie gemacht zu sein scheint: Die Seite ist in vier Spalten innerhalb eines Wrappers unterteilt. Die vier Spalten sind sichtbar durch 1px breite vertikale Linien geteilt, die vom oberen Rand der Seite bis zum unteren Rand verlaufen. Alle Elemente und Inhalte sind direkt an diesen Linien ausgerichtet, einige Inhalte sind über mehrere Spalten verteilt, andere beginnen erst an einer bestimmten Rasterlinie.
Einrichten des HTML
Zum Testen habe ich folgenedes HTML geschrieben:
<body>
<div class="wrapper">
<header class="landmark">
<a href="#" class="logo"> Biscuit! </a>
<svg><!-- ... --></svg>
</header>
<main class="landmark">
<article class="chocolate">
<h2>Chocolate cake</h2>
<p>Cookie tart cake cotton candy chocolate chocolate.</p>
<section>
<h3>Jelly beans</h3>
<p>
Jelly beans gummi bears halvah halvah croissant lemon drops donut gummi bears
candy canes. Icing sugar plum chupa chups jelly-o soufflé jelly-o pudding
lollipop. Chocolate bar muffin bonbon pie tootsie roll danish bear claw
cheesecake.
</p>
</section>
<section>
<h3>Cheesecake</h3>
<p>Powder halvah soufflé caramels soufflé chocolate cake halvah.</p>
</section>
<section>
<!-- ... -->
</section>
<section>
<!-- ... -->
</section>
</article>
</main>
<footer class="landmark">
<p>
Diving into subgrid. Created and maintained by
<a href="#">Lene</a>
</p>
<nav>
<ul role="list">
<li>
<a href="#">RSS Feed</a>
</li>
<li>
<a href="#">Follow</a>
</li>
</ul>
</nav>
<p>Crafted with semantic HTML.</p>
</footer>
</div>
</body>
Das “übergeordnete” grid-System
Zunächst lege ich die Abmessungen für den Wrapper fest.
.wrapper {
inline-size: clamp(16rem, 93vw, 120rem);
margin-inline: auto;
position: relative;
}
Dieser Wrapper soll mein seitenweites Grid-System aufnehmen. Ich möchte jedoch meine Wrapper-Klasse unangetastet lassen, sie soll sich nur um das kümmern, was ihr Name impiziert.
Mein <body>
-Element hat ein direktes child
Element, nämlich meinen Wrapper. Ich benutze diesen Selektor, um das Gittersystem separat zu definieren (eine neue Klasse mit einem entsprechenden Namen wäre auch eine Option).
body > div {
display: grid;
grid-template-columns: repeat(4, 1fr);
}
Nun wird der Wrapper in vier gleichgroße Spalten aufgeteilt, die ohne Zwischenraum direkt nebeneinander platziert werden.
Der Wrapper hat drei child
Elemente, die nun jeweils in den ersten drei Spalten platziert werden. Dies ist ihr natürliches Verhalten, da sie child
Elemente eines grids sind und sich selbst in die verfügbaren Spuren aufteilen.
Subgrid aktivieren
Ich möchte, dass alle drei Orientierungspunkte das übergeordnete Raster für sich selbst erhalten.
Aktivieren wir subgrid!
:is(header, main, footer).landmark {
display: grid;
grid-template-columns: subgrid;
}
Das funktioniert noch nicht. Ich kann in den Dev-Tools sehen, dass das Subgrid aktiv ist, aber alle drei sind immer noch in einer Spalte stecken geblieben.
Das liegt daran, dass sie immer noch direkte child
Elemente des übergeordneten Rastersystems sind.
Stattdessen möchte ich, dass sie die gesamte Breite des Wrappers einnehmen und werde daher angeben, welche Spalten des übergeordneten Rasters sie genau einnehmen sollen:
:is(header, main, footer).landmark {
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1;
}
Jetzt übernehmen unsere drei “landmarks” das grid ihres Vorfahren und platzieren ihre eigenen child
Elemente darin! Nur <main>
erstreckt sich über alle Spalten, sortiert seine Elemente aber trotzdem nur in die erste Spalte. Darauf kommen wir gleich noch zu sprechen.
Wenn ich einen Blick auf die developer tools werfe, kann ich auch sehen, dass drei “rows” automatisch erstellt wurden.
Es lohnt sich, einen genauen Blick auf die developer tools für CSS Grid zu werfen: Wenn die Seite ein Grid mit einem Subgrid enthält, wird in Firefox der Eintrag für das Subgrid unter seinem Parent im Overlay Grid Abschnitt eingerückt.
Platzieren von Elementen im Raster
Standardmäßig werden die untergeordneten Elemente auf der ersten verfügbaren Rasterlinie platziert.
header
Um das anchor
-Element im <header>
muss ich mich vorerst nicht kümmern.
Aber gemäß dem Design sollte sich das SVG an der vierten Rasterlinie ausrichten. Ich erreiche dies, indem ich mich generell an der letzten Spaltenzeile ausrichte:
header.landmark svg {
grid-column-start: -1;
}
main
Während <main>
ein subgrid ist, ist sein Kind <article>
keines: es herrscht das normale Fluslayout. Aus irgendeinem Grund hatte ich das Gefühl, dass ich Subgrid sparsam verwenden sollte. Aber dafür gibt es eigentlich keinen Grund. Also mache ich alle <article>
-Elemente, die direkte child
-Elemente von <main>
sind, ebenfalls zu subgrids!
main.landmark > article {
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1;
}
Wenn ich mir den Entwurf ansehe, sollten der Titel und der Absatz des <article>
nicht im normalen Raster platziert werden, sondern jeweils eine eigene Zeile in der ersten Spalte einnehmen.
main.landmark > article > p {
grid-row: 2;
}
Die Abschnitte in <main>
sind auf verschiedene Spalten im Desktop-Design verteilt. Ich platziere sie jetzt explizit in der ihnen zugewiesenen Zeile und Spalte. In unserem Beispiel gibt es nur Abschnitte innerhalb von main.landmark
, aber im Hinblick auf zukünftige Kompatibilität wird <main>
weitere <article>
-Elemente in einem erweiterten HTML beherbergen.
article.work > section:first-of-type {
grid-column: 2 / -1;
grid-row: 3;
}
article.work > section:nth-of-type(2) {
grid-column: 3 / -1;
grid-row: 4;
}
article.work > section:nth-of-type(3) {
grid-column: span 1 / -1;
grid-row: 5;
}
article.work > section:last-of-type {
grid-column: 3 / -1;
grid-row: 6;
}
footer
Ich möchte auch Elemente in ihrer eigenen Zeile im <footer>
platzieren. Ich kann diese zusätzliche Zeile direkt in der Landmarke definieren, und zusätzlich zu dem oben definierten Unterraster erhält sie ihren eigenen grid-template-rows
-Wert.
Das erste Child-Element soll sich über zwei Spalten und Zeilen erstrecken, das Menü und der letzte Pragraph werden rechts untereinander angeordnet.
footer.landmark {
grid-template-rows: repeat(2, auto);
}
footer.landmark p:first-of-type {
grid-column: span 2;
grid-row: 1 / -1;
}
footer.landmark p:last-of-type,
footer.landmark nav {
grid-column: span 2 / -1;
place-self: end;
}
repeat(2, auto)` bedeutet, dass es zwei Zeilen im Raster gibt, die beide die gleiche Höhe haben sollen, die dynamisch auf der Grundlage ihres Inhalts bestimmt wird.
auto" bedeutet, dass sich die Höhe der Zeilen automatisch an ihren Inhalt anpasst.
Farbige grid-Linien
Unsere Elemente sind ausgerichtet, kommen wir nun zu dem Teil, der sich zunächst einfach anhört: Die Spalten unseres Layouts sollen durch farbige Trennlinien markiert werden. Kann ich die grid-Linien nicht einfach einfärben?
Das klappt aber nicht. Ich kann die Linien in den Entwicklungswerkzeugen anzeigen, aber ich scheine keine Möglichkeit zu haben, diese Linien tatsächlich mit einer Farbe zu versehen. Allerdings habe ich ein paar Ansätze in freier Wildbahn gefunden (Sprich: Stack Overflow. Ich habe auch gewagt, Chat GTP zu fragen, aber das war völlig sinnlos). Die Ansätze wurden nicht mit Subgrid im Hinterkopf erstellt, aber ausprobieren schadet nicht.
Idee 1: Hinzufügen eines gap und einer background-color
Versuchen wir, einen gap
von 1px und eine background-color
für das übergeordnete grid hinzuzufügen.
body > div {
display: grid;
grid-template-columns: repeat(4, 1fr);
background-color: blue;
gap: 1px;
}
Anschließend wird den subgrids die normale Hintergrundfarbe wieder zurückgegeben.
:is(header, main, footer).landmark,
main.landmark > article {
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1;
background-color: white;
}
Naja, das hat nur halbwegs funktioniert. Die “intrinsically” erstellten Zeilen des übergeordneten grids sind blau eingefärbt, aber da die subgrid-container alle Spalten umfassen, sind die “gaps” nicht mehr sichtbar.
Idee 2: border-color
Kann ich allen Grid-Elementen einen Rahmen zuweisen?
body > div > * {
border-inline-start: 1px solid blue;
}
body > div > *:last-of-type {
border-inline-end: 1px solid blue;
}
Jetzt hat mein übergeordnetes Raster rechts und links einen Rand, weil dies die Bereiche sind, die nicht überlagert werden.
Was, wenn ich dies für die subgrids wiederhole?
:is(header, main, footer).landmark > *,
main.landmark > article > * {
border-inline-start: 1px solid blue;
}
:is(header, main, footer).landmark > *:last-of-type,
main.landmark > article > *:last-of-type {
border-inline-end: 1px solid blue;
}
Jetzt habe ich ein Chaos von vertikalen Linien, die an den Seiten verdoppelt sind, weil der Rahmen nur dort angewendet wird, wo es Elemente gibt.
Idee 3: background-image: linear-gradient
Mit diesem Ansatz verabschiede ich mich von dem Versuch, das grid direkt anzusprechen.
Mir ist eingefallen, dass ich ein sich wiederholendes [CSS-Muster] (https://projects.verou.me/css3patterns/#vertical-stripes) in den Hintergrund stellen könnte, da die vier Spalten völlig gleichmäßig sind. Das wäre wie eine grafische Ebene, die nur versucht, meine Rasterspalten nachzubilden.
Nach einigem Ausprobieren entschied ich mich für diese Variante:
body > div {
display: grid;
grid-template-columns: repeat(4, 1fr);
background-image: linear-gradient(to right, blue 1px, transparent 1px);
background-size: 24.95%;
background-repeat: repeat;
}
Damit wird ein linearer Farbverlauf für die Eigenschaft background-image
festgelegt, der ein sich wiederholendes Muster aus einer blauen 1-Pixel-Linie vor einem transparenten Hintergrund erzeugt und 24,95 % der Breite des Containers abdeckt. Entschuldigung für diese beliebige Zahl (auch “magische Zahl” genannt, wie ich von Christopher Kirk-Nielsen gelernt habe).
Es ist hacky, buggy und leicht daneben, aber gut genug im Moment. Ich bin ziemlich sicher, dass ich es noch ändern werde, da es nicht in jedem viewport ein schönes Ergebnis liefert.
Dies ist keine zufriedenstellende Lösung - ich hoffe, dass jemand eine bessere Idee hat und mir diese mitteilt 😬.
Update: Ideen aus der CSS-Gemeinschaft
Ich habe den Artikel auf Mastodon geteilt, und wie erhofft, haben sehr kompetente Menschen neue Ideen vorgeschlagen! 🎉
linear-gradient ohne magische Zahl
Christopher Kirk-Nielsen hat sich des Problems der magischen Zahlen angenommen und meine Lösung in eine viel sauberere verwandelt:
body > div {
--cols: 4;
--line-size: 1px;
display: grid;
grid-template-columns: repeat(var(--cols), 1fr);
background-image: linear-gradient(to right, blue var(--line-size), transparent 0);
background-size: calc((100% - var(--line-size)) / var(--cols));
}
Er hat die Anzahl der Spalten und die “Linienbreite” in Variablen gspeichert und daraus die gewünschte Breite berechnet. Das Tolle daran ist, dass es jetzt eine Beziehung zwischen dem grid und dem Muster gibt, und wir können die Linienstärke (--line-size
) und die Anzahl der Spalten (--cols
) dynamisch anpassen - sie gelten sowohl für das zugrunde liegende Rastersystem als auch für die sichtbaren Linien.
Das ist super, danke! 💚
Pseudo-Elemente als Gitternetzlinien
[Roma Komarov] (https://kizu.dev/) hat die Idee eingebracht, zusätzliche leere Elemente als Platzhalter und Pseudo-Elemente als Grid-Linien zu erstellen.
Roma setzt CSS-Pseudo-Elemente vor die Child-Elemente des <article>
-Elements und hinter das <article>
-Element selbst.
article > *::before,
article::after {
content: '';
position: absolute;
/* Bewusstes Weglassen der Einfügezeile unter Verwendung der "Ausgangsposition" dieser Elemente */
inset-block: 0;
width: 1px;
background-color: blue;
z-index: -1;
}
article::after {
right: 0;
}
Roma weist darauf hin, dass diese Methode mehr Linien als nötig erzeugt: Wenn es Elemente gibt, die auf denselben Rasterlinien beginnen, überschneiden sie sich, was sichtbar wird, wenn man einen opacity
-Wert hinzufügt:
article > *::before,
article::after {
/* alle anderen CSS-Deklarationen */
opacity: 0.1;
}
Ich liebe diese Idee, danke! 💚
Roma empfiehlt, besser leere Elemente auf dem Grid zu platzieren, da jede Änderung der aktuellen Position der <article>
-Kinder im Grid die Linien verschwinden lassen kann. Das machen wir im nächsten Abschnitt.
Platzhalter implementiert, mobile Ansicht
Ich habe Romas Idee aufgegriffen und die Platzhalter in einem weiteren Codepen implementiert. Außerdem habe ich einen “media query” für die meisten im Grid platzierten Elemente hinzugefügt, damit sie sich auf mobilen Geräten über die gesamte Breite erstrecken (und damit die Linien auch auf kleinen Viewports “funktionieren”).
Ich möchte niemanden langweilen und werde hier nicht alle CSS-Anpassungen wiederholen, wenn du die Details wissen willst, wirf einen Blick auf den code in Codepen:
Update 2: Josh Comeaus Lösung
Ein paar Tage später veröffentlichte Josh Comeau einen seiner umfangreichen Artikel, An Interactive Guide to CSS Grid. Dort geht er nicht auf Subgrid ein, aber er auch er [trägt beiläufig zu einer Lösung meines Problems bei] (https://www.joshwcomeau.com/css/interactive-guide-to-grid/#grid-construction-3).
Zur besseren Veranschaulichung seiner Beispiele hebt er die unsichtbaren Rasterlinien mit gestrichelten Linien hervor. Sein Ansatz ist ähnlich wie der von Roma: Im Zusammenhang mit Pseudo-Elementen arbeitet er nicht mit background-color
, sondern mit border-left
(Ich verwende stattdessen “logical properties”, border-inline-end
oder border-right
würden genauso funktionieren).
Seine Lösung hat einen anderen Anwendungsfall, so dass ich seine Version vereinfachen konnte.
Ich habe dies auf das letzte Beispiel mit den leeren Platzhaltern angewendet (die gestrichelten Linien von Josh habe ich beibehalten, um den Unterschied zur vorherigen Lösung deutlicher zu machen):
div.placeholder > *::before,
div.placeholder::after {
content: '';
position: absolute;
inset-block: 0;
border-inline-start: 2px dashed blue;
}
div.placeholder::after {
right: 0;
}
Mehr über subgrid
- Klassiker: mdn
- Rachel Andrew mit guten Beispielen in ihrem Artikel für 12 Days of Web, Ausgabe 2022
- Artikel auf web.dev, basierend auf der Idee eines “Makro”-Rasters auf Seitenebene
- Großartige Beispiele für Untergitter gibt es auf gridbyexample.com
- “Learn CSS Subgrid” von Ahmad Shadeed
- Michelle Barker über subrid in ihrem “Creative CSS Layout” am CSS Day 2022 (YouTube)
- …und ein Buch: CSS - The Definitive Guide
Mein Partner lachte, als er mich old school mit einem Fachbuch vor dem Computer sitzen sah. “CSS - The Definitive Guide” ist tatsächlich die erste Quelle, in der ich nachschaue, wenn ich etwas über CSS lernen oder genauer wissen möchte. Es gibt über 80 Seiten allein über grid, und ich kann es nur empfehlen.
Ich versuche meine Artikel aktuell zu halten, und natürlich kann ich mich auch irren, oder es gibt eine bessere Lösung. Wenn du etwas siehst was so nicht (mehr) stimmt, oder etwas das noch erwähnt werden sollte, kannst du den Artikel gerne bearbeiten: GitHub.