Ein paar Mal hätten Containerabfragen mir geholfen !

Avatar of Dan Christofi
Dan Christofi am

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

CSS-Containerabfragen gewinnen immer noch an Bedeutung und viele von uns experimentieren damit, auch wenn es nur für kleine Versuche oder Ähnliches ist. Sie haben eine großartige, aber noch nicht vollständige Browserunterstützung – genug, um ihre Verwendung in einigen Projekten zu rechtfertigen, aber vielleicht nicht in dem Umfang, in dem wir versucht wären, Medienabfragen aus früheren Projekten durch glänzende neue Containergrößenabfragen zu ersetzen.

Sie sind aber wirklich praktisch! Tatsächlich bin ich bereits auf ein paar Situationen gestoßen, in denen ich sie wirklich gerne verwendet hätte, aber die Unterstützungsanforderungen einfach nicht überwinden konnte. Wenn ich sie hätte verwenden können, hätte es in diesen Situationen so ausgesehen.

Alle folgenden Demos werden zum Zeitpunkt des Schreibens am besten in Chrome oder Safari angezeigt. Firefox plant, die Unterstützung in Version 109 einzuführen.

Fall 1: Kartenraster

Das musste man ja irgendwie erwarten, oder? Es ist ein so gängiges Muster, dass wir alle irgendwann darauf stoßen. Aber tatsächlich wären Containerabfragen eine enorme Zeitersparnis für mich gewesen, mit einem besseren Ergebnis, wenn ich sie anstelle von Standardmedienabfragen hätte verwenden können.

Nehmen wir an, Sie wurden beauftragt, dieses Kartenraster zu erstellen, mit der Anforderung, dass jede Karte ihr 1:1-Seitenverhältnis beibehalten muss

A four-by-three grid of card elements as a grayscale mockup.

Es ist schwieriger, als es aussieht! Das Problem ist, dass die Größe des Inhalts einer Komponente anhand der Breite des Viewports Sie der Gnade darüber ausliefert, wie die Komponente auf den Viewport reagiert – sowie wie andere übergeordnete Container darauf reagieren. Wenn Sie zum Beispiel möchten, dass die Schriftgröße einer Kartenüberschrift reduziert wird, wenn die Karte eine bestimmte Inline-Breite erreicht, gibt es keine zuverlässige Möglichkeit, dies zu tun.

Man könnte die Schriftgröße in vw-Einheiten festlegen, nehme ich an, aber die Komponente ist immer noch an die Viewport-Breite des Browsers gebunden. Und das kann Probleme verursachen, wenn das Kartenraster in anderen Kontexten verwendet wird, die möglicherweise nicht die gleichen Breakpoints haben.

In meinem realen Projekt bin ich auf einen JavaScript-Ansatz gestoßen, der

  1. Auf ein Größenänderungsereignis hören würde.
  2. Die Breite jeder Karte berechnen würde.
  3. Eine Inline-Schriftgröße zu jeder Karte basierend auf ihrer Breite hinzufügen würde.
  4. Alles im Inneren mit em-Einheiten gestalten würde.

Scheint viel Arbeit zu sein, oder? Aber es ist eine stabile Lösung, um die erforderliche Skalierung über verschiedene Bildschirmgrößen in verschiedenen Kontexten zu erreichen.

Containerabfragen wären so viel besser gewesen, weil sie uns Containerabfrageeinheiten bieten, wie die cqw-Einheit. Sie haben es wahrscheinlich schon verstanden, aber 1cqw entspricht 1% der Breite eines Containers. Wir haben auch die cqi-Einheit, die ein Maß für die Inline-Breite eines Containers ist, und cqb für die Block-Breite eines Containers. Wenn wir also einen Kartencontainer haben, der 500px breit ist, ergibt ein 50cqw-Wert 250px.

Wenn ich Containerabfragen in meinem Kartenraster hätte verwenden können, hätte ich die .card-Komponente als Container einrichten können

.card { 
  container: card / size;
}

Dann hätte ich einen inneren Wrapper mit padding einrichten können, der mit 10% der .card-Breite unter Verwendung der cqw-Einheit skaliert

.card__inner { 
  padding: 10cqw; 
} 

Das ist eine schöne Möglichkeit, den Abstand zwischen den Rändern der Karte und ihrem Inhalt konsistent zu skalieren, unabhängig davon, wo die Karte bei einer bestimmten Viewport-Breite verwendet wird. Keine Medienabfragen erforderlich!

Noch eine Idee? Verwenden Sie cqw-Einheiten für die Schriftgröße des inneren Inhalts und wenden Sie dann Padding in em-Einheiten an

.card__inner { 
  font-size: 5cqw; 
  padding: 2em;
} 

5cqw ist ein beliebiger Wert – nur einer, auf den ich mich geeinigt habe. Dieses Padding entspricht immer noch 10cqw, da die em-Einheit relativ zur .card__inner-Schriftgröße ist!

Haben Sie das bemerkt? Die 2em bezieht sich auf die 5cqw-Schriftgröße, die auf demselben Container gesetzt ist. Container funktionieren anders, als wir es gewohnt sind, da em-Einheiten sich auf den font-size-Wert desselben Elements beziehen. Aber was mir schnell aufgefallen ist, ist, dass sich Containerabfrageeinheiten auf das nächstgelegene übergeordnete Element beziehen, das ebenfalls ein Container ist.

Zum Beispiel skaliert 5cqw in diesem Beispiel nicht basierend auf der Breite des .card-Elements

.card { 
  container: card / size; 
  container-name: card; 
  font-size: 5cqw; 
}

Vielmehr skaliert es zu jedem nächstgelegenen übergeordneten Element, das als Container definiert ist. Deshalb habe ich einen .card__inner-Wrapper eingerichtet.

Fall 2: Abwechselnde Layouts

Ich brauchte in einem anderen Projekt eine weitere Kartenkomponente. Diesmal musste die Karte von einem Querformat-Layout zu einem Hochformat-Layout wechseln... dann wieder zu Querformat und wieder zu Hochformat, wenn der Bildschirm kleiner wird.

Showing four states of a card element changing between portrait and landscape layouts at various breakpoints.

Ich habe die schmutzige Arbeit geleistet, diese Komponente in diesen beiden spezifischen Viewport-Bereichen auf Hochformat umzustellen (shout out an die neue Medienabfrage-Bereichssyntax!), aber das Problem ist wieder, dass sie dann an die gesetzten Medienabfragen, ihren übergeordneten Container und alles andere, was auf die Breite des Viewports reagieren könnte, gebunden ist. Wir wollen etwas, das unter allen Bedingungen funktioniert, ohne sich Sorgen machen zu müssen, wo der Inhalt umbricht!

Containerabfragen hätten dies dank der @container-Regel zum Kinderspiel gemacht

.info-card {
  container-type: inline-size;
  container-name: info-card;
}

@container info-card (max-width: 500px) {
  .info-card__inner {
    flex-direction: column;
  }
}

Eine Abfrage, unendliche Fluidität

Aber Moment mal! Es gibt etwas, auf das Sie vielleicht achten möchten. Insbesondere könnte es schwierig sein, eine Containerabfrage wie diese innerhalb eines prop-basierten Designsystems zu verwenden. Zum Beispiel könnte diese .info-card-Komponente Kindkomponenten enthalten, die sich auf Props verlassen, um ihr Aussehen zu ändern.

Warum ist das ein großes Problem? Das Hochformat des Kartenlayouts erfordert möglicherweise das alternative Styling, aber Sie können JavaScript-Props nicht mit CSS ändern. Daher riskieren Sie, die erforderlichen Stile zu duplizieren. Ich habe dies tatsächlich in einem anderen Artikel berührt und wie man es umgeht. Wenn Sie Containerabfragen für einen erheblichen Teil Ihres Stylings verwenden müssen, müssen Sie möglicherweise Ihr gesamtes Designsystem darauf basieren, anstatt zu versuchen, es in ein bestehendes Designsystem zu quetschen, das stark auf Medienabfragen setzt.

Fall 3: SVG-Striche

Hier ist ein weiteres sehr gängiges Muster, das ich kürzlich verwendet habe und bei dem Containergrößenabfragen zu einem ausgefeilteren Produkt geführt hätten. Nehmen wir an, Sie haben eine Ikone, die mit einer Überschrift verbunden ist

<h2>
  <svg>
    <!-- SVG stuff -->
  </svg> 
  Heading
</h2>

Es ist ziemlich einfach, die Ikone mit der Größe des Titels zu skalieren, sogar ohne Medienabfragen. Das Problem ist jedoch, dass die stroke-width der SVG bei kleinerer Größe zu dünn werden kann, um gut wahrgenommen zu werden, und vielleicht bei einer super dicken Strichstärke bei größerer Größe zu viel Aufmerksamkeit erregt.

Ich musste für jede Icon-Instanz Klassen erstellen und anwenden, um ihre Größe und Strichstärke zu bestimmen. Das ist in Ordnung, wenn die Ikone neben einer Überschrift steht, die mit einer festen Schriftgröße gestaltet ist, schätze ich, aber es ist nicht so gut, wenn man mit fließenden Typen arbeitet, die sich ständig ändern.

A lockup of a hexagon icon and heading at three different sizes, from large to small.

Die Schriftgröße der Überschrift kann auf der Breite des Viewports basieren, sodass sich die SVG-Ikone entsprechend anpassen muss, wo ihr Strich in jeder Größe funktioniert. Man könnte die Strichstärke relativ zur font-size der Überschrift machen, indem man sie in em-Einheiten festlegt. Aber wenn Sie eine bestimmte Reihe von Strichstärken haben, an die Sie sich halten müssen, dann würde dies nicht funktionieren, weil sie sonst linear skaliert – es gibt keine Möglichkeit, sie ohne Rückgriff auf Medienabfragen nach Viewport-Breite auf einen bestimmten stroke-width-Wert an bestimmten Punkten anzupassen.

Aber hier ist, was ich getan hätte, wenn ich damals den Luxus von Containerabfragen gehabt hätte

.icon {
  container: icon / size; 
  width: 1em; 
  height: 1em; 
}

.icon svg {
  width: 100%; 
  height: 100%; 
  fill: none; 
  stroke: #ccc; 
  stroke-width: 0.8; 
}

@container icon (max-width: 70px) {
  .icon svg {
    stroke-width: 1.5; 
  }
}
@container icon (max-width: 35px) {
  .icon svg {
    stroke-width: 3;
  }
}

Vergleichen Sie die Implementierungen und sehen Sie, wie die Containerabfrage-Version den Strich der SVG an die spezifischen Breiten anpasst, die ich basierend auf der Breite des Containers wünsche.

Bonus: Andere Arten von Containergrößenabfragen

OK, ich bin dem zwar noch nicht in einem echten Projekt begegnet. Aber als ich Informationen über Containerabfragen durchging, fiel mir auf, dass es zusätzliche Dinge gibt, nach denen wir in einem Container abfragen können, die sich auf die Größe oder die physischen Abmessungen des Containers beziehen.

Die meisten Beispiele, die ich gesehen habe, fragen die width, max-width und min-width, height, block-size und inline-size ab, wie ich es in diesem Artikel getan habe.

@container info-card (max-width: 500px) {
  .info-card__inner {
    flex-direction: column;
  }
}

Aber MDN beschreibt zwei weitere Dinge, gegen die wir abfragen können. Eines ist die orientation, was perfekt Sinn ergibt, weil wir sie ständig in Medienabfragen verwenden. Bei Containerabfragen ist es nicht anders

@media screen (orientation: landscape) { 
  .info-card__inner {
    /* Style away! */
  }
} 

@container info-card (orientation: landscape) { 
  .info-card__inner {
    /* Style away! */
  }
} 

Das andere? Es ist tatsächlich das aspect-ratio

@container info-card (aspect-ratio: 3/2) { 
  .info-card__inner {
    /* Style away! */
  }
} 

Hier ist eine bearbeitbare Demo, mit der Sie beides ausprobieren können

Ich habe für keines von beiden bisher einen guten Anwendungsfall gefunden. Wenn Sie Ideen haben oder der Meinung sind, dass es Ihnen in Ihren Projekten geholfen hätte, lassen Sie es mich in den Kommentaren wissen!