Erweiterbare Abschnitte innerhalb eines CSS-Grids

Avatar of Kev Bonett
Kev Bonett am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Ich liebe CSS Grid. Ich liebe, wie wir mit nur wenigen Codezeilen vollständig responsive Grid-Layouts erzielen können, oft ganz ohne Media Queries. Ich fühle mich ziemlich wohl dabei, CSS Grid zu nutzen, um interessante Layouts zu erstellen und dabei den HTML-Markup sauber und einfach zu halten.

Aber kürzlich wurde ich mit einem einzigartigen UI-Dilemma konfrontiert, das es zu lösen galt. Im Wesentlichen könnte jede gegebene Grid-Zelle einen Button haben, der einen anderen, größeren Bereich öffnet, der ebenfalls Teil des Grids ist. Aber diese neue, größere Grid-Zelle musste

  1. direkt unter der Zelle liegen, die sie geöffnet hat, und
  2. eine volle Breite haben.

Es stellt sich heraus, dass es dafür eine schöne Lösung gibt, und im Geiste von CSS Grid selbst, erfordert sie nur ein paar Codezeilen. In diesem Artikel werde ich drei einzeilige CSS Grid "Tricks" kombinieren, um dies zu lösen. Kein JavaScript nötig.

Eine Erklärung des eigentlichen Problems, das ich lösen muss

Hier ist ein minimalistisches UI-Beispiel dessen, was ich tun musste

Dies ist unser tatsächliches Produktkarten-Grid, wie es in unserer Storybook-Komponentenbibliothek gerendert wird

A grid of product cards in a three by two layout. Each card has a placeholder gray image, product name, descriptions, price, and small text.

Jede Produktkarte musste eine neue „Schnellansicht“-Schaltfläche erhalten, die beim Klicken

  • dynamisch eine neue Vollbildkarte (mit detaillierteren Produktinformationen) direkt unter der angeklickten Produktkarte „einfügt“,
  • ohne das bestehende Kartenraster zu stören (d. h. die DOM-Quellenreihenfolge und die visuelle Reihenfolge der gerenderten Karten im Browser beizubehalten) und
  • immer noch vollständig responsiv zu sein.

Hmm... war das mit unserer aktuellen CSS Grid-Implementierung überhaupt möglich?

Sicherlich müsste ich auf JavaScript zurückgreifen, um die Positionen der Karten neu zu berechnen und sie zu verschieben, besonders bei Browser-Resizes? Richtig?

Google war keine Hilfe. Ich konnte nichts finden, was mir geholfen hätte. Selbst die Suche nach „Schnellansicht“-Implementierungen ergab nur Beispiele, die Modals oder Overlays zur Darstellung der eingefügten Karte verwendeten. Schließlich ist ein Modal oft die einzige Wahl in Situationen wie dieser, da es den Benutzer auf die neuen Inhalte konzentriert, ohne den Rest der Seite stören zu müssen.

Ich habe über das Problem nachgedacht und schließlich eine funktionierende Lösung gefunden, indem ich einige der leistungsfähigsten und nützlichsten Funktionen von CSS Grid kombiniert habe.

CSS Grid Trick #1

Ich habe den ersten Trick bereits für unser Standard-Grid-System verwendet, und das Produktkarten-Grid ist eine spezielle Instanz dieses Ansatzes. Hier ist ein (vereinfachter) Code

.grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, 20rem);
}

Die „Geheimzutat“ in diesem Code ist grid-template-columns: repeat(auto-fit, 20rem);, was uns ein Grid mit Spalten (in diesem Beispiel 20rem breit) gibt, die automatisch im verfügbaren Platz angeordnet werden und in die nächste Zeile umbrechen, wenn nicht genug Platz ist.

Neugierig auf auto-fit vs. auto-fill? Sara Soueidan hat eine wunderbare Erklärung gegeben, wie das funktioniert. Sara erklärt auch, wie Sie minmax() einbinden können, um die Spaltenbreiten „flexibel“ zu machen, aber für die Zwecke dieses Artikels wollte ich aus Gründen der Einfachheit feste Spaltenbreiten definieren.

CSS Grid Trick #2

Als Nächstes musste ich eine neue Vollbildkarte in das Raster aufnehmen

.fullwidth {
  grid-column: 1 / -1;
}

Dieser Code funktioniert, weil grid-template-columns in Trick #1 ein „explizites“ Grid erstellt, sodass es möglich ist, Start- und Endspalten für die .fullwidth-Karte zu definieren, wobei 1 / -1 bedeutet: „beginne in Spalte 1 und spanne alle Spalten bis zur allerletzten“.

Großartig. Eine Vollbildkarte wurde in das Raster eingefügt. Aber… jetzt gibt es Lücken oberhalb der Vollbildkarte.

Two rows of four rectangles. All of the rectangles are light gray and numbered, except one that has a wheat-colored background and another box beneath it containing text, and taking up the full container width.

CSS Grid Trick #3

Füllen der Lücken – das habe ich schon einmal mit einem Pseudo-Masonry-Ansatz gemacht

.grid {
  grid-auto-flow: dense;
}

Das war's! Das gewünschte Layout ist erreicht.

Die Eigenschaft grid-auto-flow steuert, wie der CSS Grid Auto-Placement-Algorithmus funktioniert. In diesem Fall versucht der dense Packing-Algorithmus, Löcher früher im Grid zu füllen.

  • Alle unsere Grid-Spalten haben die **gleiche Breite**. Dense Packing funktioniert auch, wenn die Spaltenbreiten flexibel sind, z. B. durch die Verwendung von minmax(20rem, 1f).
  • Alle unsere Grid-„Zellen“ haben die **gleiche Höhe** in jeder Zeile. Dies ist das Standardverhalten von CSS Grid. Der Grid-Container hat implizit align-items: stretch, wodurch Zellen 100% der verfügbaren Zeilenhöhe einnehmen.

Das Ergebnis all dessen ist, dass die Lücken in unserem Grid gefüllt sind – und das Schöne daran ist, dass die **ursprüngliche Quellenreihenfolge** in der gerenderten Ausgabe erhalten bleibt. Dies ist aus Gründen der Barrierefreiheit wichtig.

Siehe MDN für eine vollständige Erklärung von wie CSS Grid Auto-Placement funktioniert.

Die vollständige Lösung

Diese drei kombinierten Tricks bieten eine einfache Layout-Lösung, die nur sehr wenig CSS erfordert. Keine Media Queries und kein JavaScript notwendig.

Aber… wir brauchen doch immer noch JavaScript?

Ja, das tun wir. Aber nicht für Layout-Berechnungen. Es ist rein **funktional** für die Verwaltung von Klickereignissen, dem Fokusstatus, der Anzeige der eingefügten Karte usw.

Zu Demozwecken im Prototyp wurden die Vollbildkarten im HTML an den richtigen Stellen im DOM hartcodiert, und das JavaScript schaltet einfach ihre Anzeigeeigenschaften um.

In einer Produktionsumgebung würde die eingefügte Karte jedoch wahrscheinlich per JavaScript abgerufen und an der richtigen Stelle platziert werden. Grid-Layouts für etwas wie Produkte auf einer E-Commerce-Website haben tendenziell sehr umfangreiche DOMs, und wir möchten eine unnötige Vergrößerung des Seitenumfangs durch viele zusätzliche „versteckte“ Inhalte vermeiden.

Schnellansichten sollten als **progressive enhancement** betrachtet werden. Wenn JavaScript also nicht geladen wird, wird der Benutzer einfach zur entsprechenden Produktdetailseite weitergeleitet.

Barrierefreiheitsaspekte

Ich setze mich leidenschaftlich dafür ein, korrekte semantische HTML-Markups zu verwenden, aria-Eigenschaften hinzuzufügen, wenn sie absolut notwendig sind, und sicherzustellen, dass die Benutzeroberfläche sowohl mit der Tastatur als auch mit einem Screenreader funktioniert.

Hier ist also eine Zusammenfassung der Überlegungen, die in die möglichst barrierefreie Gestaltung dieses Musters eingeflossen sind

  • Das Produktkartenraster verwendet eine <ul><li>-Konstruktion, da wir eine Liste von Produkten anzeigen. Assistive Technologien (z. B. Screenreader) verstehen daher, dass es eine Beziehung zwischen den Karten gibt, und Benutzer werden über die Anzahl der Elemente in der Liste informiert.
  • Die Produktkarten selbst sind <article>-Elemente mit entsprechenden Überschriften usw.
  • Die HTML-Quellenreihenfolge wird für die Karten beibehalten, wenn die .fullwidth-Karte eingefügt wird, was eine gute natürliche Tabulatorreihenfolge in die eingefügten Inhalte und wieder hinaus zur nächsten Karte ergibt.
  • Das gesamte Kartenraster ist in eine aria-live-Region eingebettet, damit DOM-Änderungen für Screenreader angekündigt werden..
  • Das Fokusmanagement stellt sicher, dass die eingefügte Karte den Tastaturfokus erhält und nach dem Schließen der Karte der Tastaturfokus auf den Button zurückgesetzt wird, der ursprünglich die Sichtbarkeit der Karte ausgelöst hat.

Obwohl dies im Prototyp nicht demonstriert wird, könnten diese zusätzlichen Verbesserungen in jede Produktionsimplementierung integriert werden

  • Stellen Sie sicher, dass die eingefügte Karte bei Fokus einen entsprechenden Bezeichner hat. Dies kann so einfach sein wie eine Überschrift als erstes Element im Inhalt.
  • Binden Sie die ESC-Taste, um die eingefügte Karte zu schließen.
  • Scrollen Sie das Browserfenster, damit die eingefügte Karte vollständig im Viewport sichtbar ist.

Zusammenfassung

Also, was denkst du?

Dies könnte eine nette Alternative zu Modals sein, wenn wir zusätzliche Inhalte offenlegen möchten, ohne dabei den gesamten Viewport zu kapern. Dies könnte auch in anderen Situationen interessant sein – denken Sie an Bildunterschriften in einem Bildraster, Hilfetexte usw. Es könnte sogar eine Alternative zu einigen Fällen sein, in denen wir normalerweise auf <details>/<summary> zurückgreifen würden (da wir wissen, dass diese nur in bestimmten Kontexten am besten geeignet sind).

Auf jeden Fall bin ich daran interessiert, wie Sie dies nutzen könnten oder wie Sie es anders angehen würden. Lassen Sie es mich in den Kommentaren wissen!

Aktualisierungen

Erstens freue ich mich sehr, dass dieser Artikel für andere Front-End-Entwickler hilfreich war. Ich wusste, dass ich nicht der Einzige sein konnte, der vor einem ähnlichen Dilemma stand.

Zweitens habe ich nach konstruktivem Feedback einige der spezifischen Zugänglichkeitsüberlegungen durchgestrichen und meine CodePen-Demo mit folgenden Änderungen aktualisiert:

  • Es ist nicht notwendig, das Kartenraster in eine aria-live-Region einzubetten. Stattdessen habe ich die Schaltflächen zum Öffnen und Schließen der Schnellansicht als „Umschaltflächen“ fungieren lassen, mit entsprechenden aria-expanded- und aria-controls-Attributen. Ich verwende dieses Muster zwar für Offenlegungs-Widgets (Anzeigen/Verbergen, Tabs, Akkordeons), aber in diesem Fall stellte ich mir ein Verhalten vor, das eher einem Modal-Interface ähnelt, wenn auch inline statt eines Overlays. (Danke an Adrian für den Tipp!)
  • Ich konzentriere mich nicht mehr programmatisch auf die eingefügte Karte. Stattdessen füge ich einfach ein tabindex="0" hinzu, sodass ein Tastaturnutzer wählen kann, ob er zur eingefügten Karte wechselt oder einfach wieder auf die „Umschaltfläche“ klickt, um sie zu schließen.
  • Ich glaube immer noch, dass die Verwendung einer <ul><li>-Konstruktion für das Raster ein geeigneter Ansatz für eine **Liste** von Produkt**karten** ist. Die gewährten Semantiken weisen auf eine explizite Beziehung zwischen den Karten hin.