CSS-Containerabfragen: Anwendungsfälle und Migrationsstrategien

Veröffentlicht: 2022-03-10
Kurze Zusammenfassung ↬ CSS-Container-Abfragen bringen Medienabfragen näher an die Zielelemente selbst und ermöglichen es ihnen, sich an praktisch jeden gegebenen Container oder jedes Layout anzupassen. In diesem Artikel behandeln wir die Grundlagen von CSS-Container-Abfragen und wie man sie heute mit progressiver Erweiterung oder Polyfills verwendet.

Wenn wir Medienabfragen für ein UI-Element schreiben, beschreiben wir immer, wie dieses Element in Abhängigkeit von den Bildschirmabmessungen gestaltet ist. Dieser Ansatz funktioniert gut, wenn die Reaktionsfähigkeit der Medienabfrage des Zielelements nur von der Größe des Darstellungsbereichs abhängen soll. Sehen wir uns das folgende Beispiel für ein responsives Seitenlayout an.

Beispiel für ein responsives Seitenlayout. Das linke Bild zeigt das Desktop-Layout mit einer Viewport-Breite von 1280 Pixel und das rechte Bild zeigt das mobile Layout mit einer Viewport-Breite von 568 Pixel.
Beispiel für ein responsives Seitenlayout. Das linke Bild zeigt das Desktop-Layout mit einer Viewport-Breite von 1280 Pixel und das rechte Bild zeigt das mobile Layout mit einer Viewport-Breite von 568 Pixel. (Große Vorschau)

Responsives Webdesign (RWD) ist jedoch nicht auf ein Seitenlayout beschränkt – die einzelnen UI-Komponenten verfügen in der Regel über Medienabfragen, die je nach Größe des Darstellungsbereichs ihren Stil ändern können.

Beispiel für eine responsive Produktkartenkomponente. Das linke Bild zeigt eine Komponente mit einer Ansichtsfensterbreite von 720 Pixel und das rechte Bild zeigt ein Komponentenlayout mit einer Ansichtsfensterbreite von 568 Pixel.
Beispiel für eine responsive Produktkartenkomponente. Das linke Bild zeigt eine Komponente mit einer Ansichtsfensterbreite von 720 Pixel und das rechte Bild zeigt ein Komponentenlayout mit einer Ansichtsfensterbreite von 568 Pixel. (Große Vorschau)

Möglicherweise haben Sie bereits ein Problem mit der vorherigen Aussage bemerkt – das Layout einzelner UI-Komponenten hängt oft nicht ausschließlich von den Abmessungen des Ansichtsfensters ab. Während das Seitenlayout ein Element ist, das eng mit den Dimensionen des Ansichtsfensters verbunden ist und eines der obersten Elemente in HTML ist, können UI-Komponenten in verschiedenen Kontexten und Containern verwendet werden. Wenn Sie darüber nachdenken, ist der Darstellungsbereich nur ein Container, und UI-Komponenten können in anderen Containern mit Stilen verschachtelt werden, die sich auf die Abmessungen und das Layout der Komponente auswirken.

Beispiel für ein Seitenlayout mit derselben Produktkarten-UI-Komponente im 3-Spalten-Raster des oberen Abschnitts und der unteren Abschnittsliste.
Beispiel für ein Seitenlayout mit derselben Produktkarten-UI-Komponente im 3-Spalten-Raster des oberen Abschnitts und der unteren Abschnittsliste. (Große Vorschau)

Auch wenn dieselbe Produktkartenkomponente sowohl im oberen als auch im unteren Bereich verwendet wird, hängen die Stile der Komponenten nicht nur von den Abmessungen des Ansichtsfensters ab, sondern auch vom Kontext und den CSS-Eigenschaften des Containers (wie dem Raster im Beispiel), in denen sie platziert sind.

Natürlich können wir unser CSS so strukturieren, dass wir die Stilvariationen für verschiedene Kontexte und Container unterstützen, um das Layoutproblem manuell anzugehen. Im schlimmsten Fall würde diese Variante mit Stilüberschreibung hinzugefügt, was zu Codeduplizierung und Spezifitätsproblemen führen würde.

 .product-card { /* Default card style */ } .product-card--narrow { /* Style variation for narrow viewport and containers */ } @media screen and (min-width: 569px) { .product-card--wide { /* Style variation for wider viewport and containers */ } }

Dies ist jedoch eher eine Problemumgehung für die Einschränkungen von Medienabfragen als eine geeignete Lösung. Beim Schreiben von Medienabfragen für UI-Elemente versuchen wir, einen „magischen“ Viewport-Wert für einen Haltepunkt zu finden, wenn das Zielelement Mindestabmessungen hat, bei denen das Layout nicht unterbrochen wird. Kurz gesagt, wir verknüpfen einen „magischen“ Dimensionswert des Ansichtsfensters mit dem Dimensionswert des Elements . Dieser Wert unterscheidet sich normalerweise von der Dimension des Ansichtsfensters und ist anfällig für Fehler, wenn sich die Abmessungen des inneren Containers oder das Layout ändern.

Beispiel dafür, dass Medienabfragen nicht zuverlässig mit Elementabmessungen verknüpft werden können. Verschiedene CSS-Eigenschaften können sich auf Elementabmessungen innerhalb eines Containers auswirken. In diesem Beispiel ist die Containerauffüllung zwischen den beiden Bildern unterschiedlich.
Beispiel dafür, dass Medienabfragen nicht zuverlässig mit Elementabmessungen verknüpft werden können. Verschiedene CSS-Eigenschaften können sich auf Elementabmessungen innerhalb eines Containers auswirken. In diesem Beispiel ist die Containerauffüllung zwischen den beiden Bildern unterschiedlich. (Große Vorschau)

Das folgende Beispiel zeigt genau dieses Problem – obwohl ein responsives Produktkartenelement implementiert wurde und in einem Standardanwendungsfall gut aussieht, sieht es kaputt aus, wenn es in einen anderen Container mit CSS-Eigenschaften verschoben wird, die sich auf die Elementabmessungen auswirken. Jeder zusätzliche Anwendungsfall erfordert das Hinzufügen von zusätzlichem CSS-Code , was zu doppeltem Code, aufgeblähtem Code und schwer zu wartendem Code führen kann.

Siehe Pen [Product Cards: Various Containers](https://codepen.io/smashingmag/pen/eYvWVxz) von Adrian Bece.

Siehe die Stift-Produktkarten: Verschiedene Behälter von Adrian Bece.

Dies ist eines der Probleme, die Containerabfragen zu beheben versuchen. Containerabfragen erweitern die Funktionalität vorhandener Medienabfragen um Abfragen, die von den Dimensionen des Zielelements abhängen. Es gibt drei Hauptvorteile bei der Verwendung dieses Ansatzes:

  • Container-Abfragestile werden abhängig von den Dimensionen des Zielelements selbst angewendet. UI-Komponenten können sich an jeden gegebenen Kontext oder Container anpassen.
  • Entwickler müssen nicht nach einem „Magic Number“-Darstellungsdimensionswert suchen, der eine Darstellungsfeld-Medienabfrage mit einer Zieldimension einer UI-Komponente in einem bestimmten Container oder einem bestimmten Kontext verknüpft.
  • Es müssen keine zusätzlichen CSS-Klassen oder Medienabfragen für verschiedene Kontexte und Anwendungsfälle hinzugefügt werden.
„Die ideale responsive Website ist ein System aus flexiblen, modularen Komponenten, die wiederverwendet werden können, um in mehreren Kontexten eingesetzt zu werden.“

– „Container-Abfragen: Noch einmal zum Durchbruch“, Mat Marquis

Bevor wir tief in Containerabfragen eintauchen, müssen wir uns die Browserunterstützung ansehen und sehen, wie wir die experimentelle Funktion in unserem Browser aktivieren können.

Browser-Unterstützung

Containerabfragen sind eine experimentelle Funktion, die zum Zeitpunkt der Erstellung dieses Artikels derzeit in der Chrome Canary-Version verfügbar war. Wenn Sie den CodePen-Beispielen in diesem Artikel folgen und sie ausführen möchten, müssen Sie Containerabfragen in der folgenden Einstellungs-URL aktivieren.

 chrome://flags/#enable-container-queries
Containerabfragen
(Große Vorschau)

Falls Sie einen Browser verwenden, der keine Containerabfragen unterstützt, wird neben der CodePen-Demo ein Bild bereitgestellt, das das beabsichtigte Arbeitsbeispiel zeigt.

Mehr nach dem Sprung! Lesen Sie unten weiter ↓

Arbeiten mit Containerabfragen

Containerabfragen sind nicht so einfach wie normale Medienabfragen. Wir müssen unserem UI-Element eine zusätzliche Zeile CSS-Code hinzufügen, damit Containerabfragen funktionieren, aber dafür gibt es einen Grund, den wir als Nächstes behandeln werden.

Containment-Eigenschaft

Die CSS- contain Eigenschaft wurde den meisten modernen Browsern hinzugefügt und hat zum Zeitpunkt des Schreibens dieses Artikels eine anständige Browserunterstützung von 75 %. Die Eigenschaft " contain " wird hauptsächlich zur Leistungsoptimierung verwendet, indem sie dem Browser mitteilt, welche Teile (Teilbäume) der Seite als unabhängig behandelt werden können und die Änderungen an anderen Elementen in einem Baum nicht beeinflussen. Auf diese Weise rendert der Browser bei einer Änderung in einem einzelnen Element nur diesen Teil (Teilbaum) und nicht die ganze Seite. Mit contain Eigenschaftswerten können wir angeben, welche Art von Containment wir verwenden möchten – layout , size oder paint .

Es gibt viele großartige Artikel über die contain „Containen“, die verfügbare Optionen und Anwendungsfälle ausführlicher beschreiben, daher werde ich mich nur auf Eigenschaften konzentrieren, die sich auf Containerabfragen beziehen.

Was hat die zur Optimierung verwendete CSS-Zufriedenheitseigenschaft mit Containerabfragen zu tun? Damit Containerabfragen funktionieren, muss der Browser wissen, ob eine Änderung im untergeordneten Layout des Elements auftritt, dass er nur diese Komponente neu rendern soll. Der Browser weiß, dass er den Code in der Containerabfrage auf die übereinstimmende Komponente anwenden muss, wenn die Komponente gerendert wird oder sich die Dimension der Komponente ändert.

Wir verwenden die layout​ style​ für die contain​ “, aber wir benötigen auch einen zusätzlichen Wert, der dem Browser signalisiert, auf welcher Achse die Änderung stattfinden wird.

  • inline-size
    Containment auf der Inline-Achse. Es wird erwartet, dass dieser Wert deutlich mehr Anwendungsfälle hat, also wird er zuerst implementiert.
  • block-size
    Eindämmung auf Blockachse. Es befindet sich noch in der Entwicklung und ist derzeit nicht verfügbar.

Ein kleiner Nachteil der contain -Eigenschaft ist, dass unser Layout-Element ein untergeordnetes Element eines contain -Elements sein muss, was bedeutet, dass wir eine zusätzliche Verschachtelungsebene hinzufügen.

 <section> <article class="card"> <div class="card__wrapper"> <!-- Card content --> </div> </article> </section>
 .card { contain: layout inline-size style; } .card__wrapper { display: grid; grid-gap: 1.5em; grid-template-rows: auto auto; /* ... */ }

Beachten Sie, dass wir diesen Wert nicht zu einem weiter entfernten übergeordneten section hinzufügen und den Container so nah wie möglich am betroffenen Element halten.

„Leistung ist die Kunst, Arbeit zu vermeiden und jede Arbeit so effizient wie möglich zu gestalten. In vielen Fällen geht es darum, mit dem Browser zu arbeiten, nicht dagegen.“

– „Rendering-Leistung“, Paul Lewis

Deshalb sollten wir dem Browser die Änderung korrekt signalisieren. Das Umschließen eines entfernten übergeordneten Elements mit einer contain -Eigenschaft kann kontraproduktiv sein und sich negativ auf die Seitenleistung auswirken. Im schlimmsten Fall, wenn die Eigenschaft " contain " missbraucht wird, kann das Layout sogar kaputt gehen und der Browser wird es nicht richtig darstellen.

Container-Abfrage

Nachdem die contain -Eigenschaft zum Card-Element-Wrapper hinzugefügt wurde, können wir eine Container-Abfrage schreiben. Wir haben einem Element mit der Klasse card eine contain -Eigenschaft hinzugefügt, sodass wir jetzt jedes seiner untergeordneten Elemente in eine Containerabfrage einbeziehen können.

Genau wie bei normalen Medienabfragen müssen wir eine Abfrage mit den Eigenschaften min-width oder max-width definieren und alle Selektoren innerhalb des Blocks verschachteln. Wir verwenden jedoch das Schlüsselwort @container anstelle von @media , um eine Containerabfrage zu definieren.

 @container (min-width: 568px) { .card__wrapper { align-items: center; grid-gap: 1.5em; grid-template-rows: auto; grid-template-columns: 150px auto; } .card__image { min-width: auto; height: auto; } }

Sowohl card__wrapper als card__image Element sind untergeordnete Elemente des card Elements, für das die contain -Eigenschaft definiert ist. Wenn wir die regulären Medienabfragen durch Containerabfragen ersetzen, die zusätzlichen CSS-Klassen für schmale Container entfernen und das CodePen-Beispiel in einem Browser ausführen, der Containerabfragen unterstützt, erhalten wir das folgende Ergebnis.

In diesem Beispiel ändern wir nicht die Größe des Ansichtsfensters, sondern das Containerelement <section> selbst, auf das die CSS-Eigenschaft resize angewendet wurde. Abhängig von den Behälterabmessungen wechselt die Komponente automatisch zwischen den Layouts.
In diesem Beispiel ändern wir nicht die Größe des Ansichtsfensters, sondern das Containerelement <section> selbst, auf das die CSS-Eigenschaft resize angewendet wurde. Abhängig von den Behälterabmessungen wechselt die Komponente automatisch zwischen den Layouts. (Große Vorschau)

Siehe Pen [Product Cards: Container Queries](https://codepen.io/smashingmag/pen/PopmQLV) von Adrian Bece.

Siehe Pen Product Cards: Container Queries von Adrian Bece.

Bitte beachten Sie, dass Containerabfragen derzeit nicht in den Chrome-Entwicklertools angezeigt werden , was das Debuggen von Containerabfragen etwas schwierig macht. Es wird erwartet, dass dem Browser in Zukunft die richtige Debugging-Unterstützung hinzugefügt wird.

Sie können sehen, wie uns Containerabfragen ermöglichen, robustere und wiederverwendbare UI-Komponenten zu erstellen, die sich an praktisch jeden Container und jedes Layout anpassen lassen. Von einer richtigen Browserunterstützung für Containerabfragen ist das Feature allerdings noch weit entfernt. Versuchen wir mal, ob wir Containerabfragen mit progressiver Erweiterung implementieren können.

Progressive Verbesserung & Polyfills

Mal sehen, ob wir CSS-Klassenvariationen und Medienabfragen einen Fallback hinzufügen können. Wir können CSS-Funktionsabfragen mit der @supports Regel verwenden, um verfügbare Browserfunktionen zu erkennen. Wir können jedoch nicht nach anderen Abfragen suchen, daher müssen wir eine Prüfung auf einen Wert für contain: layout inline-size style hinzufügen. Wir müssen davon ausgehen, dass Browser, die inline-size Eigenschaften unterstützen, auch Containerabfragen unterstützen.

 /* Check if the inline-size value is supported */ @supports (contain: inline-size) { .card { contain: layout inline-size style; } } /* If the inline-size value is not supported, use media query fallback */ @supports not (contain: inline-size) { @media (min-width: 568px) { /* ... */ } } /* Browser ignores @container if it's not supported */ @container (min-width: 568px) { /* Container query styles */ }

Dieser Ansatz kann jedoch zu doppelten Stilen führen, da dieselben Stile sowohl von der Containerabfrage als auch von der Medienabfrage angewendet werden. Wenn Sie sich entscheiden, Containerabfragen mit progressiver Erweiterung zu implementieren, sollten Sie einen CSS-Präprozessor wie SASS oder einen Postprozessor wie PostCSS verwenden, um das Duplizieren von Codeblöcken zu vermeiden, und stattdessen CSS-Mixins oder einen anderen Ansatz verwenden.

Siehe Pen [Produktkarten: Containerabfragen mit progressiver Verbesserung](https://codepen.io/smashingmag/pen/zYZwRXZ) von Adrian Bece.

Siehe Pen-Produktkarten: Containerabfragen mit progressiver Erweiterung von Adrian Bece.

Da sich diese Containerabfragespezifikation noch in einer experimentellen Phase befindet, ist es wichtig zu bedenken, dass die Spezifikation oder Implementierung in zukünftigen Versionen möglicherweise geändert wird.

Alternativ können Sie Polyfills verwenden, um einen zuverlässigen Fallback bereitzustellen. Es gibt zwei JavaScript-Polyfills, die ich hervorheben möchte, die derzeit anscheinend aktiv gepflegt werden und die erforderlichen Container-Abfragefunktionen bereitstellen:

  • cqfill von Jonathan Neal
    JavaScript-Polyfill für CSS und PostCSS
  • react-container-query von Chris Garcia
    Benutzerdefinierter Hook und Komponente für React

Migrieren von Medienabfragen zu Containerabfragen

Wenn Sie sich entscheiden, Containerabfragen in einem vorhandenen Projekt zu implementieren, das Medienabfragen verwendet, müssen Sie den HTML- und CSS-Code umgestalten. Ich habe festgestellt, dass dies die schnellste und einfachste Möglichkeit ist, Containerabfragen hinzuzufügen und gleichzeitig einen zuverlässigen Fallback für Medienabfragen bereitzustellen. Schauen wir uns das vorherige Kartenbeispiel an.

 <section> <div class="card__wrapper card__wrapper--wide"> <!-- Wide card content --> </div> </section> /* ... */ <aside> <div class="card__wrapper"> <!-- Narrow card content --> </div> </aside>
 .card__wrapper { display: grid; grid-gap: 1.5em; grid-template-rows: auto auto; /* ... */ } .card__image { /* ... */ } @media screen and (min-width: 568px) { .card__wrapper--wide { align-items: center; grid-gap: 1.5em; grid-template-rows: auto; grid-template-columns: 150px auto; } .card__image { /* ... */ } }

Umschließen Sie zunächst das Stamm-HTML-Element, auf das eine Medienabfrage angewendet wurde, mit einem Element, das die Eigenschaft „ contain “ hat.

 <section> <article class="card"> <div class="card__wrapper"> <!-- Card content --> </div> </article> </section>
 @supports (contain: inline-size) { .card { contain: layout inline-size style; } }

Verpacken Sie als Nächstes eine Medienabfrage in einer Funktionsabfrage und fügen Sie eine Containerabfrage hinzu.

 @supports not (contain: inline-size) { @media (min-width: 568px) { .card__wrapper--wide { /* ... */ } .card__image { /* ... */ } } } @container (min-width: 568px) { .card__wrapper { /* Same code as .card__wrapper--wide in media query */ } .card__image { /* Same code as .card__image in media query */ } }

Obwohl diese Methode zu einer gewissen Aufblähung des Codes und doppeltem Code führt, können Sie durch die Verwendung von SASS oder PostCSS das Duplizieren von Entwicklungscode vermeiden, sodass der CSS-Quellcode wartbar bleibt.

Sobald Containerabfragen die richtige Browserunterstützung erhalten, sollten Sie in Betracht ziehen, @supports not (contain: inline-size) zu entfernen und weiterhin ausschließlich Containerabfragen zu unterstützen.

Stephanie Eckles hat kürzlich einen großartigen Artikel über Containerabfragen veröffentlicht, der verschiedene Migrationsstrategien behandelt. Ich empfehle, es für weitere Informationen zum Thema zu lesen.

Anwendungsszenarien

Wie wir aus den vorherigen Beispielen gesehen haben, werden Containerabfragen am besten für hochgradig wiederverwendbare Komponenten mit einem Layout verwendet, das vom verfügbaren Containerplatz abhängt und die in verschiedenen Kontexten verwendet und zu verschiedenen Containern auf der Seite hinzugefügt werden können.

Weitere Beispiele sind (Beispiele erfordern einen Browser, der Containerabfragen unterstützt):

  • Modulare Komponenten wie Karten, Formularelemente, Banner etc.
  • Anpassbare Layouts
  • Paginierung mit unterschiedlichen Funktionalitäten für Mobile und Desktop
  • Lustige Experimente mit CSS-Größenänderung

Fazit

Sobald die Spezifikation implementiert und in Browsern umfassend unterstützt wurde, könnten Containerabfragen zu einer bahnbrechenden Funktion werden. Es ermöglicht Entwicklern, Abfragen auf Komponentenebene zu schreiben und die Abfragen näher an die verwandten Komponenten zu verschieben, anstatt die entfernten und kaum verwandten Medienabfragen des Ansichtsfensters zu verwenden. Dies wird zu robusteren, wiederverwendbaren und wartbaren Komponenten führen, die sich an verschiedene Anwendungsfälle, Layouts und Container anpassen können.

Derzeit befinden sich Containerabfragen noch in einer frühen, experimentellen Phase und die Implementierung ist anfällig für Änderungen. Wenn Sie heute damit beginnen möchten, Containerabfragen in Ihren Projekten zu verwenden, müssen Sie sie mithilfe der progressiven Erweiterung mit Funktionserkennung hinzufügen oder ein JavaScript-Polyfill verwenden. Beide Fälle führen zu einem gewissen Overhead im Code. Wenn Sie sich also in dieser frühen Phase für die Verwendung von Containerabfragen entscheiden, planen Sie unbedingt ein Refactoring des Codes ein, sobald die Funktion allgemein unterstützt wird.

Verweise

  • „Containerabfragen: Eine Kurzanleitung“ von David A. Herron
  • „Sag Hallo zu CSS-Containerabfragen“, Ahmad Shadeed
  • „CSS-Eindämmung in Chrome 52“, Paul Lewis
  • „Unterstützung von Browsern bei der Optimierung mit der CSS Contain-Eigenschaft“, Rachel Andrew