Modernes CSS bietet uns ständig neue, einfachere Wege, um alte Probleme zu lösen. Doch oft lösen diese neuen Funktionen nicht nur bestehende Probleme, sondern eröffnen uns auch völlig neue Möglichkeiten.
Container Queries sind eines dieser Dinge, die neue Möglichkeiten eröffnen, aber da sie dem alten Vorgehen mit Media Queries *sehr* ähnlich sehen, ist unser erster Instinkt, sie auf die gleiche oder zumindest eine sehr ähnliche Weise zu verwenden.
Wenn wir das tun, nutzen wir jedoch nicht die Vorteile, wie „smart“ Container Queries im Vergleich zu Media Queries sind!
Aufgrund der Wichtigkeit von Media Queries für die Ära des responsiven Webdesigns möchte ich nichts Böses über sie sagen... aber Media Queries sind dumm. Nicht dumm im Konzept, sondern dumm in dem Sinne, dass sie nicht viel wissen. Tatsächlich gehen die meisten Leute davon aus, dass sie mehr wissen, als sie tatsächlich tun.
Lassen Sie uns dieses einfache Beispiel verwenden, um zu veranschaulichen, was ich meine.
html {
font-size: 32px;
}
body {
background: lightsalmon;
}
@media (min-width: 35rem) {
body {
background: lightseagreen;
}
}
Welche Viewport-Größe hätte der Hintergrund, um seine Farbe zu ändern? Wenn Sie 1120px Breite sagen – was das Produkt aus 35 mal 32 ist, für diejenigen, die sich nicht die Mühe gemacht haben, nachzurechnen – sind Sie mit dieser Vermutung nicht allein, aber Sie lägen auch falsch.
Erinnern Sie sich, als ich sagte, dass Media Queries nicht viel wissen? Sie wissen nur zwei Dinge:
- die Größe des Viewports und
- die Schriftgröße des Browsers.
Und wenn ich sage, *die Schriftgröße des Browsers*, meine ich nicht die Root-Schriftgröße in Ihrem Dokument, weshalb 1120px im obigen Beispiel falsch war.
Die Schriftgröße, die sie betrachten, ist die anfängliche Schriftgröße, die vom Browser stammt, bevor *irgendwelche* Werte, einschließlich der User-Agent-Styles, angewendet werden. Standardmäßig sind das 16px, obwohl Benutzer dies in ihren Browsereinstellungen ändern können.
Und ja, **das ist beabsichtigt**. Die Media Query Spezifikation besagt:
Relative Längeneinheiten in Media Queries basieren auf dem anfänglichen Wert, was bedeutet, dass Einheiten niemals auf Ergebnissen von Deklarationen basieren.
Dies mag eine seltsame Entscheidung sein, aber wenn es nicht so funktionieren würde, was würde passieren, wenn wir dies tun würden?
html {
font-size: 16px;
}
@media (min-width: 30rem) {
html {
font-size: 32px;
}
}
Wenn die Media Query die Root-font-size betrachten würde (wie die meisten annehmen), würden Sie eine Schleife laufen, wenn der Viewport 480px breit wird, bei der die font-size immer größer wird und dann immer wieder kleiner wird.
Container Queries sind viel intelligenter
Während Media Queries diese Einschränkung haben, und das aus gutem Grund, müssen sich Container Queries nicht um diese Art von Problem kümmern und das eröffnet viele interessante Möglichkeiten!
Nehmen wir zum Beispiel an, wir haben ein Gitter, das bei kleineren Größen gestapelt sein soll, aber bei größeren Größen drei Spalten. Mit Media Queries müssen wir uns irgendwie durch „Magic Number“ den genauen Punkt finden, an dem dies geschehen soll. Mit einer Container Query können wir die minimale Größe bestimmen, die eine Spalte haben soll, und das wird immer funktionieren, da wir die Containergröße betrachten.
Das bedeutet, dass wir keine magische Zahl für den Breakpoint benötigen. **Wenn ich drei Spalten mit einer Mindestgröße von 300px haben möchte, weiß ich, dass ich drei Spalten haben kann, wenn der Container 900px breit ist**. Wenn ich das mit einer Media Query tun würde, würde es nicht funktionieren, denn wenn mein Viewport 900px breit ist, ist mein Container meistens kleiner als das.
Aber noch besser, wir können auch jede gewünschte Einheit verwenden, denn Container Queries können im Gegensatz zu Media Queries die Schriftgröße des Containers selbst betrachten.
Für mich ist ch perfekt für so etwas. Mit ch kann ich sagen: „Wenn ich genug Platz habe, damit jede Spalte mindestens 30 Zeichen breit ist, möchte ich drei Spalten haben.“
Wir können die Berechnung selbst durchführen, wie folgt:
.grid-parent { container-type: inline-size; }
.grid {
display: grid;
gap: 1rem;
@container (width > 90ch) {
grid-template-columns: repeat(3, 1fr);
}
}
Und das funktioniert ziemlich gut, wie Sie in diesem Beispiel sehen können.
Als weiterer Bonus, dank Miriam Suzanne, habe ich kürzlich gelernt, dass man calc() in Media- und Container-Queries einschließen kann, so dass Sie anstatt selbst zu rechnen, dies einschließen können: @container (width > calc(30ch * 3)), wie Sie in diesem Beispiel sehen können.
Ein praktischerer Anwendungsfall
Eines der nervigen Dinge bei der Verwendung von Container Queries ist, **dass ein definierter Container vorhanden sein muss**. Ein Container kann sich nicht selbst abfragen, daher benötigen wir eine zusätzliche Umhüllung oberhalb des Elements, das wir mit einer Container Query auswählen möchten. Sie können in den obigen Beispielen sehen, dass ich einen Container außerhalb meines Gitters benötigte, damit dies funktioniert.
Noch nerviger ist es, wenn Grid- oder Flex-Kinder ihr Layout ändern sollen, je nachdem, wie viel Platz sie haben, nur um festzustellen, dass dies nicht wirklich funktioniert, wenn das übergeordnete Element der Container ist. Anstatt diesen Grid- oder Flex-Container als definierten Container zu verwenden, müssen wir **jeden Grid- oder Flex-Artikel in einen Container einpacken**, wie hier:
<div class="grid">
<div class="card-container">
<div class="card">
</div>
<div class="card-container">
<div class="card">
</div>
<div class="card-container">
<div class="card">
</div>
</div>
.card-container { container-type: inline-size; }
Das ist im Großen und Ganzen nicht *so* schlimm, aber es ist schon etwas nervig.
Es gibt jedoch Wege, dies zu umgehen!
Wenn Sie beispielsweise repeat(auto-fit, ...) verwenden, können Sie das Hauptgitter als Container verwenden!
.grid-auto-fit {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(min(30ch, 100%)), 1fr);
container-type: inline-size;
}
Da wir die Mindestgröße einer Spalte von 30ch kennen, können wir diese Information nutzen, um einzelne Grid-Elemente neu zu gestalten, je nachdem, wie viele Spalten wir haben.
/* 2 columns + gap */
@container (width > calc(30ch * 2 + 1rem)) { ... }
/* 3 columns + gaps */
@container (width > calc(30ch * 3 + 2rem)) { ... }
Ich habe dies in diesem Beispiel verwendet, um die Stile des ersten Kindes in meinem Gitter zu ändern, je nachdem, ob wir eine, zwei oder drei Spalten haben.
Und während das Ändern der Hintergrundfarbe von etwas für Demos großartig ist, können wir damit natürlich noch viel mehr tun.
Der Nachteil dieses Ansatzes
Der einzige Nachteil, den ich bei diesem Ansatz gefunden habe, ist, dass wir keine benutzerdefinierten Eigenschaften für die Breakpoints verwenden können, was die DX davon erheblich verbessern würde.
Das sollte sich *irgendwann* ändern, wenn man bedenkt, dass benutzerdefinierte Media Queries in der Entwurfsfassung des Media Queries Level 5 Spezifikationen des Herausgebers sind, aber sie sind schon eine Weile dort, ohne dass es eine Bewegung von Browsern gibt, sodass es noch lange dauern könnte, bis wir sie verwenden können.
Und obwohl meine Meinung ist, dass benutzerdefinierte Eigenschaften diese sowohl lesbarer als auch einfacher zu aktualisieren machen würden, eröffnet es genügend Möglichkeiten, dass es sich auch ohne sie lohnt.
Was ist mit Flexbox?
Bei Flexbox sind die Flex-Elemente die, die das Layout definieren, daher ist es etwas seltsam, dass die Größen, die wir auf die Elemente anwenden, für die Breakpoints wichtig sind.
Es *kann* trotzdem funktionieren, aber **es gibt ein großes Problem, das auftreten kann, wenn Sie dies mit Flexbox tun**. Bevor wir uns das Problem ansehen, hier ein kurzes Beispiel, wie wir dies mit Flexbox zum Laufen bringen können.
.flex-container {
display: flex;
gap: 1rem;
flex-wrap: wrap;
container-type: inline-size;
}
.flex-container > * {
/* full-width at small sizes */
flex-basis: 100%;
flex-grow: 1;
/* when there is room for 3 columns including gap */
@container (width > calc(200px * 3 + 2rem)) {
flex-basis: calc(200px);
}
}
In diesem Fall habe ich px verwendet, um zu zeigen, dass es auch funktioniert, aber Sie könnten jede Einheit verwenden, wie ich es bei den Grid-Beispielen getan habe.
Dies könnte wie etwas aussehen, für das Sie auch eine Media Query verwenden können – Sie können auch die calc() darin verwenden! – aber das würde in einer nur funktionieren, wenn das übergeordnete Element eine Breite hat, die der Viewport-Breite entspricht, was meistens nicht der Fall ist.
Das bricht, wenn die Flex-Elemente Polsterung haben
Viele Leute realisieren es nicht, aber der Flexbox-Algorithmus berücksichtigt keine Polsterung oder Ränder, selbst wenn Sie Ihr box-sizing ändern. Wenn Sie padding bei Ihren Flex-Elementen haben, müssen Sie im Grunde mit „Magic Number“ vorgehen, um es zum Laufen zu bringen.
Hier ist ein Beispiel, bei dem ich etwas Polsterung hinzugefügt habe, aber sonst nichts geändert habe, und Sie werden feststellen, dass an einer Stelle eines dieser seltsamen Zwei-Spalten-Layouts mit einer unten gestreckten Spalte auftritt.
Aus diesem Grund neige ich dazu, diese Art von Ansatz häufiger mit Grid als mit Flexbox zu verwenden, aber es gibt definitiv Situationen, in denen er immer noch funktionieren kann.
Wie zuvor, da wir wissen, wie viele Spalten wir haben, können wir dies nutzen, um dynamischere und interessantere Layouts zu erstellen, je nach verfügbarem Platz für ein bestimmtes Element oder Elemente.
Eröffnung einiger interessanter Möglichkeiten
Ich habe erst angefangen, mit dieser Art von Dingen herumzuspielen, und ich habe festgestellt, dass sie einige neue Möglichkeiten eröffnet hat, die wir mit Media Queries nie hatten, und das macht mich gespannt darauf, was noch möglich ist!
Liebe den Artikel und liebe das Artikelformat (zusätzlich zu Ihrem üblichen YouTube-Format). Flexibler/zugänglicher für mich, ihn in meinem eigenen Tempo zu lesen (und wiederzulesen). Danke!
Kombinieren Sie diese Technik mit :has(), um GUI-Logik (Öffnen/Schließen, Erweitern/Einklappen, Anzeigen/Verbergen usw.) zu handhaben, und wir sind vielleicht in der Lage, eine Benutzeroberfläche zu erstellen, die sich in Echtzeit und nach Bedarf selbst aufbaut.
Sehr kreativ ist diese Seite, auf der man viele CSS-Tags lernen kann.
Wie würden Sie eine CSS-Grid-Bedingung schreiben, basierend darauf, wann Elemente den horizontalen (Inline-)Platz ausgeht und umbrechen, anstatt willkürliche Einheiten festzulegen, die dann berücksichtigt und gepflegt werden müssen?
Das würde den Wartungsaufwand für Header beispielsweise reduzieren. Wenn der Inline-Platz ausgeht, wird stattdessen ein Hamburger-Menü angezeigt, anstatt umzubrechen.