CSS/Media Queries/Container Queries
Während Medienabfragen eine Methode zur Abfrage von Aspekten des Browsers oder der Geräteumgebung bieten, in der ein Dokument angezeigt wird (z. B. Abmessungen des Viewports oder Benutzereinstellungen), ermöglichen Containerabfragen das Testen von Aspekten von Elementen innerhalb des Dokuments (z. B. Rahmenabmessungen oder berechnete Stile).
Es gibt zwei unterschiedliche Arten von Containerabfragen: Größenabfragen und Style-Abfragen. Style-Abfragen prüfen, ob eine beliebige CSS-Eigenschaft einen bestimmten Wert besitzt und sind prinzipiell auf jedem HTML Element möglich, während es für eine Größenabfrage erforderlich ist, dem abgefragten Element einen Container-Typ zuzuweisen. Wir werden gleich besprechen, wie man das macht.
Die eigentliche Containerabfrage erfolgt dann mit der @-Regel @container und ähnelt einer @media-Abfrage.
Inhaltsverzeichnis
Einsatz
Bei der Erstellung eines responsiven Seitenlayouts hast du das Problem zu lösen, dass bestimmte Teile deines Layouts in Abhängigkeit vom verfügbaren Platz angepasst werden müssen. Das kann eine Schriftgröße sein, das Setzen oder Entfernen eines Randes, oder das Austauschen eines Grid-Templates.
Solche Abfragen waren bisher (außer mit JavaScript) nur mit @media-Abfragen möglich. Diese haben aber den Nachteil, dass sie ausschließlich die Viewport-Größe prüfen. Ein komponentenbasierendes Design wird dadurch extrem erschwert, weil du für die @media-Querys ständig das Gesamtlayout betrachten musst. Und wenn die gleiche Komponente in unterschiedlichen Größen mehrfach auf einer Seite exisitert, brauchst du für jede Verwendung der Komponente eigene Abfragen.[1]
Wenn du Container-Querys verwendest, kann sich die Komponente hingegen an die Größe ihres Umfeldes anpassen, und die erforderlichen Abfragen müssen nur einmal für die Komponente erstellt werden. Schau dir das folgende Seitenlayout an, das Cards zur Darstellung von Inhalten verwendet. Diese Cards können größer oder kleiner dargestellt sein. Mit @media-Querys wäre dieses Layout ein Alptraum!
Anwendungsfall | Container Queries | Media Queries |
---|---|---|
Benutzereinstellungen | ||
Global Layout Stylings | ||
Kontext-isolierte und wiederverwendbare Komponenten |
||
Viewport-relative Komponenten | ||
Print-CSS |
Anwendungsbeispiel
Cards sind eines der Entwurfsmuster, die für Container Queries geradezu prädestiniert scheinen. Sie sollen sich nicht nur responsiv über die Seitenbreite verteilen; sondern eben auch je nach verfügbarem Platz ihr Aussehen ändern.
container-type
Um Container-Größenabfragen zu verwenden, benötigst du vor allem einen Größencontainer. Dieser Container stellt einen Bezugspunkt, oder containment-context, für Container-Abfragen dar. Um Rekursionen zu vermeiden, ist es deshalb nicht möglich, die Eigenschaften dieses Container als Ergebnis der Container-Abfrage zu beeinflussen. Du müssen deshalb möglicherweise ein div-Element mehr in dein HTML einbauen, als es ohne Containerabfrage nötig wäre.
Um ein Element zum Größencontainer zu machen, gebest du ihm die CSS-Eigenschaft container-type. Der Wert, den du für diese Eigenschaft setzt, hängt davon ab, ob du die Größe lediglich in Richtung der Inlineachse abfragen möchten, oder ob du Abfragen in Richtung von Inline- und Blockachse benötigst. Für Abfragen in Inline-Richtung setze container-type: inline-size;
, und für Abfragen in beiden Richtungen container-type: size;
.
Diese Zuweisung erzeugt automatisch auch eine Containment Box für Layout, Style und Größe (als inline-size
oder size
, je nach gesetzten Typ) – was auch bedeutet, dass die (Inline-)Größe des Containers nicht mehr von seinem Inhalt abhängig sein kann, sondern von außen festgelegt werden muss.
@container
Die eigentliche Containerabfrage ähnelt einer Medienabfrage und wird durch die @-Regel @container erstellt. Die innerhalb dieser @-Regel notierten CSS-Regeln sind nur wirksam, wenn zusätzlich zu ihrem Selektor auch die bei @container notierte Bedingung zutrifft. Betrachten wir als Einführungsbeispiel eine Card-Komponente, wie oben beschrieben.
<div class=".card-container">
<article class="card">
<h2>...</h2>
<p>...</p>
</article>
</div>
Der Inhalt dieser Card (der jetzt nicht weiter von Interesse ist) soll zweispaltig dargestellt werden, sobald die Card genügend breit dargestellt wird. Nehmen wir an, 40em
wären „genügend“.
.card-container {
container-type: inline-size;
}
.card {
/* weitere Eigenschaften */
}
@container (width >= 40em) {
.card {
display: grid;
grid-template-columns: 2fr 1fr;
}
}
Das div-Element mit der Klasse .card-container
stellt den Größencontainer für die Card dar. Durch den Containertyp inline-size
sind Abfragen auf die Inline-Größe dieses Containers möglich, also auf die CSS-Eigenschaften inline-size
oder – bei horizontaler Schreibrichtung – width
.
Dieses div muss nicht völlig nutzlos sein. Du kannst ihm weitere CSS Eigenschaften wie Rand, Padding oder Hintergrund zuweisen, um die Darstellung der Karte zu gestalten, aber kannst diese Eigenschaften lediglich nicht vom Ergebnis der Containerabfrage abhängig machen.
Da in diesem Beispiel die Card auf Grid-Layout umgeschaltet werden soll, muss das Container-Element vom Rahmenelement der Card (dem <article>
-Element) getrennt werden.
Die @container-Regel definiert nun die Bedingung, der erfüllt sein muss, damit die in ihr notierten Regeln angewendet werden sollen. Dazu dient die aus @media-Abfragen bekannte Syntax für Medienmerkmale, entweder mit der alten min-/max-Technik oder mit der neueren Operatorenschreibweise. Wir werden hier mit Operatoren arbeiten.
Innerhalb der @container-Regel befindet sich die CSS-Regel, die das Umschalten auf Grid-Layout vornimmt. Der Browser wird versuchen, diese Regel auf alle Elemente anzuwenden, die die Klasse .card
besitzen. Aber bevor er das wirklich tut, ermittelt er für jedes dieser Elemente einzeln, ob es sich in einem Größencontainer befindet, auf den die Bedingung zutrifft, die bei @container notiert ist. Und nur dann, wenn die Bedingung zutrifft, wird die Regel angewendet.
Diese Arbeitsweise ist sehr flexibel, kann aber auch verwirren, denn die @container-Regel legt nicht fest, gegen welchen Container geprüft wird. Und manchmal möchte man auch ausdrücklich festlegen, dass nicht jeder Container in Frage kommt. Dazu gibt es eine weitere Eigenschaft für das Containerelement.
container-name
Wenn du auf einer Seite mehrere Container erstellen willst, kann es Zuordnungsschwierigkeiten geben. Manchmal möchte man, dass die Regeln in einem @container-Block nur dann angewendet werden, wenn es sich um einen ganz bestimmten Container handelt.
Um das zu erreichen, kannst du dem Container mit der container-name-Eigenschaft einen oder mehrere Namen zuordnen und in der Container-Abfrage einen dieser Namen angeben. Die Abfrage trifft dann nur noch auf die Container zu, die diesen Namen enthalten.
.card-container {
container-type: inline-size;
container-name: card;
}
@container card (width >= 40em){
.card {
display: grid;
grid-template-columns: 2fr 1fr;
}
}
Mit dem in container-name
festgelegten Namen card
kannst du sicherstellen, dass die Zweispaltigkeit nur noch angewendet wird, wenn die Card in einem card
-Container ist. Möglicherweise gibt es auch noch einen list
-Container, in dem die Card sich lediglich als Einzeiler darstellen soll.
Ein ähnliches Verhalten ließe sich auch durch Verwendung einer ID oder Klasse auf dem Containerelement erreichen, die dann den Selektoren der CSS-Regeln hinzugefügt wird. Damit verändert sich aber auch die Spezifität der Regeln in der Container-Abfrage, was den Vorrang der Regeln untereinander stören kann. Eine Abfrage auf den Containernamen hingegen beeinflusst die Spezifität nicht.
container
Weil container-name
eigentlich immer zusammen mit container-type
verwendet wird, gibt es auch eine Shorthand-Eigenschaft namens container, die die beiden zusammenfasst:
.card-container {
container: card / inline-size;
}
@container card (width > 40em){
.card {
display: grid;
grid-template-columns: 2fr 1fr;
}
}
Der Wert der container-Eigenschaft besteht aus der Liste der gewünschten Containernamen, einem /
, um das Ende der Namensliste anzuzeigen, und dem Schlüsselwort für den Containertyp.
Mehrspaltiges Layout mit CSS Container Queries
In diesen zwei Beispielen soll nun die Funktionsweise gezeigt werden; zuerst mit nur einer Karte, später als komplexeres Beispiel:
<article class="card-container">
<div class="card">
<h3 class="card__title">Karte</h3>
<div class="content">
<p>...</p>
</div>
<img src="Landscape.svg" at="Landschaft mit Berg und Kirche als Symbolbild">
</div>
</article>
Als wrapper-Element verwenden wir ein article-Element, das eine passende Klasse erhält.
Unsere Karte besteht aus einem div mit einer h3-Überschrift, einem Inhaltsbereich (.content
) und einem Bild.
.card-container {
container: card/ inline-size;
}
@container card (width >= 30em) {
.card-container .card {
--color: pink;
grid-template-columns: 33% 1fr;
grid-template-areas: "image title"
"image content";
gap: 1em;
}
.card-container .card__title {
grid-area: title;
}
.card-container .card p {
grid-area: content;
}
.card-container .card img {
grid-area: image;
}
}
@container (width >= 50em) {
.card-container .card {
--color: skyblue;
grid-template-columns: 50% 12em 1fr;
grid-template-areas: "image title content";
}
}
.card {
display: grid;
}
Die Karte befindet sich im Container, den du mit der Maus größer oder kleiner ziehen kannst. Sobald die Containerabfrage greift, wird das Grid Layout aufgebaut und die Inhalte nebeneinander dargestellt. Zusätzlich wird die Hintergrundfarbe geändert.
Diese Karte soll nun in verschiedenen Containern verwendet werden. Es so aber weiterhin eine CSS-Festlegung „für ae Gelegenheiten geben“:
<article>
<div class="card-container">
<div class="card">
...
</div>
</div>
<div class="card-container">
<div class="card">
...
</div>
</div>
...
</article>
<aside class="card-container">
<div class="card">
...
</div>
</aside>
<footer class="card-container">
<div class="card">
...
</div>
</footer
Das CSS für den article verteilt die Karten nun über den verfügbaren Platz:
article {
display: flex;
flex-wrap: wrap;
gap: 1em;
}
article .card-container {
flex: 1 0 30%;
gap: 1em;
}
article .card {
min-height: 14em;
}
Die .card-container verteilen sich mit flex auf den verfügbaren Platz. Für schmale Viewports müsste man eine weitere Medienabfrage mit weniger Spalten einbauen.
Zwischenfazit:
Ich bin nicht ganz zufrieden mit dem Ergebnis.
Ursprünglich wollte ich die cards im article mit grid-template-columns: repeat(auto-fit, minmax(min(12em, 100%), 1fr));
verteilen. Dabei bemerkte ich aber dass die Containerabfrage die Breite des Elternelements und nicht, wie von mir erwünscht - des vom Grid erzeugten Rasterfelds abfragt. Eigentlich logisch, erfordert hier aber die Anlage eines Wrapper-Elements um jede einzelne Karte.
Andererseits wollte ich den verfügbaren Patz auf die Karten aufteilen, so dass ich mit der Flexbox-Lösung besser fahre. Für schmale Viewports wäre hier eine weitere media query nötig, was ich neben den wrapper-Elementen eigentlich vermeiden wollte.
Durch die Wrapper-Elemente haben die einzelnen Karten je nach Inhalt unterschiedliche Höhen - etwas, was mit Grid auch automatisch ausgeglichen würde. Ich würde dieses Beispiel am liebsten mit einem normalen Grid und einer eigenen Festlegung für Karten in einem Grid ohne Container Queries bauen. In der Zukunft finden sich hier vielleicht neue Ansätze.
--Matthias Scharwies (Diskussion) 06:50, 4. Aug. 2023 (CEST)
Style-Abfragen
Eine Container-Stilabfrage ist eine @container-Abfrage, die berechnete Stile des Container-Elements auswertet, wie sie in einer oder mehreren funktionalen style()-Notationen definiert sind.[2] [3]
@container style(color: green) and style(background-color: transparent) {
/* <stylesheet> */
}
Sobald die Browserunterstützung besser ist, wird ein Beispiel entwickelt.
--Matthias Scharwies (Diskussion) 06:30, 4. Apr. 2024 (CEST)
Container-Query-Einheiten
Das CSS Containment Module enthält neben den oben besprochenen Eigenschaften auch zahlreiche neue Einheiten. Container-Query-Längeneinheiten geben eine Länge relativ zu den Abmessungen eines Abfragecontainers an. Komponenten, die Längeneinheiten relativ zu ihrem Container verwenden, können flexibler in verschiedenen Containern verwendet werden, ohne dass konkrete Längenwerte neu berechnet werden müssen.[4][5]
Wer mit den Einheiten vw, vh etc. für Viewports bereits vertraut ist, wird hier wenig Startschwierigkeiten haben.
Wenn sie sich innerhalb eines Containers befinden, werden die Containerabmessungen anstelle der Viewport-Abmessungen verwendet. Wenn sie sich nicht innerhalb eines Containers befinden, verwenden sie Werte der Viewport-Abmessungen.
Ein Vergleich mit „normalen“ relativen rems zeigt die Ersparnis:
.card__title {
font-size: 1rem;
}
/* The horizontal style, v1 */
@container (min-width: 400px) {
.card__title {
font-size: 1.15rem;
}
}
/* The horizontal style, v2 */
@container (min-width: 600px) {
.card__title {
font-size: 1.25rem;
}
}
/* The hero style */
@container (min-width: 800px) {
.card__title {
font-size: 2rem;
}
}
.card__title {
font-size: clamp(1rem, 3cqw, 2rem);
}
Im linken Beispiel wird die Schriftgröße von .card__title
mit 1rem festgesetzt und dann durch mehrere Media queries passend zur Viewportgröße festgelegt. Auf der rechten Seite wird dies durch eine einzige Deklaration erreicht. Die Schriftgröße wird mit einer clamp()-Funktion mit cqw auf 3% der Containerbreite festgelegt, minimal 1rem, maximal aber 2rem.[6]
Schriftgröße abhängig vom Container ändern
Oft möchte man die Überschrift über die gesamte Breite laufen lassen. Im pixelgenauen Layout kann so etwas mit Magic Numbers fest eingestellt werden. Jede Änderung der Breite zerschießt dann das Layout. Hier kann die Schriftgröße fließend „mitwachsen“:
<div class="container">
<div class="card"> … </div>
</div>
.container {
container-type: inline-size;
}
.card {
font-size: clamp(1em, 18cqi, 5em);
}
Als Wert für font-size wird in der CSS-Funktion clamp() eine variable Schriftgröße mit einer Mindest- und einer Maximalgröße angegeben. Die mittlere Angabe in clamp()
legt den Orientierungspunkt fest. Mit 18cqi
gestalten wir eine dynamische Schriftgröße auf Grundlage von 18% der Inline-Größe des Containers.
Polyfill
Mittlerweile (Stand März 2024) werden Container Queries von den letzten Versionen der Browser (Anteil: ca. 92%) unterstützt. Für ältere Versionen kannst du einen Polyfill von Chris Coyier verwenden.[7][8]
- GoogleChromeLabs auf github: container-query-polyfill von surma
// Support Test
const supportsContainerQueries = "container" in document.documentElement.style;
// Conditional Import
if (!supportsContainerQueries) {
import("https://cdn.skypack.dev/container-query-polyfill");
}
Siehe auch
Eigenschaften
Container-Einheiten
@-Regeln
Weblinks
- ↑ Wann Media Queries an ihre Grenzen stoßen im Blog von kulturbanause
- ↑ CSSWG: 4.4. Container Queries: the @container rule (CSS Containment Module Level 3)
- ↑ MDN: Container style queries
- ↑ MDN: Container query length units
- ↑ Container Units Should Be Pretty Handy (css-tricks.com)
- ↑ CSS Container Query Units von Ahmad Shadeed, 18 Sep 2021
- ↑ css-tricks: A New Container Query Polyfill That Just Works vom 06.01.2022
- ↑ github: GoogleChromeLabs /container-query-polyfill von surma
- csswg: CSS Containment Module Level 3
- MDN: CSS Container Queries
- css-tricks: Next Gen CSS: @container Una Kravets, May 11, 2021
- ishadeed.com: CSS Container Query Units vom 18.09.2021
- smashing-magazine: A Primer On CSS Container Queries Stephanie Eckles, 11.05.2021
- Ahmad Shadeed hat sich schon früh mit Containerabfragen beschäftigt und verfügt über eine wachsende Sammlung von Beispielen, die auf alltäglichen Mustern basieren.[1][2]
deutsch
- kulturbanause: CSS Container Queries mit @container