Containerabfragen sind eine neue CSS-Funktion, mit der Sie eine Stillogik schreiben können, die auf Elemente eines übergeordneten Elements (z. B. Breite oder Höhe) ausgerichtet ist, um dessen untergeordnete Elemente zu gestalten. Vor Kurzem wurde ein großes Update für Polyfill veröffentlicht, das parallel zur Unterstützung von Browsern veröffentlicht wurde.
In diesem Beitrag erhältst du einen Einblick in die Funktionsweise von Polyfills, die Herausforderungen, die damit bewältigt werden können, und die Best Practices, mit denen du deinen Besuchern eine hervorragende Nutzererfahrung bieten kannst.
Details
Transpilation
Wenn der CSS-Parser in einem Browser auf eine unbekannte at-Regel stößt, z. B. die brandneue @container
-Regel, wird sie einfach verworfen, als ob sie nie existieren würde. Daher muss der Polyfill als Erstes eine @container
-Abfrage in etwas übertragen, das nicht verworfen wird.
Der erste Schritt der Transpilierung besteht darin, die übergeordnete @container
-Regel in eine @media-Abfrage umzuwandeln. Dies stellt in erster Linie sicher, dass die Inhalte gruppiert bleiben. beispielsweise bei der Verwendung von CSSOM-APIs und beim Aufrufen der CSS-Quelle.
@container (width > 300px) { /* content */ }
@media all { /* content */ }
Vor Containerabfragen hatte ein Autor im Preisvergleichsportal keine Möglichkeit, Regelgruppen willkürlich zu aktivieren oder zu deaktivieren. Um dieses Verhalten mit Polyfills zu füllen, müssen auch die Regeln innerhalb einer Containerabfrage transformiert werden. Jeder @container
erhält eine eigene eindeutige ID (z. B. 123
), mit der die einzelnen Selektoren so transformiert werden, dass sie nur angewendet werden, wenn das Element ein cq-XYZ
-Attribut mit dieser ID hat. Dieses Attribut wird zur Laufzeit vom Polyfill festgelegt.
@container (width > 300px) { .card { /* ... */ } }
@media all { .card:where([cq-XYZ~="123"]) { /* ... */ } }
Beachten Sie die Verwendung der Pseudoklasse :where(...)
. Durch das Hinzufügen eines zusätzlichen Attributselektors wird normalerweise die Spezifität des Selektors erhöht. Mit der Pseudoklasse kann die zusätzliche Bedingung unter Beibehaltung der ursprünglichen Spezifität angewendet werden. Warum das so wichtig ist, sehen Sie am folgenden Beispiel:
@container (width > 300px) {
.card {
color: blue;
}
}
.card {
color: red;
}
Aufgrund dieses CSS-Codes sollte ein Element mit der Klasse .card
immer color: red
haben, da die spätere Regel immer die vorherige Regel mit demselben Selektor und derselben Spezifität überschreibt. Das Transpilieren der ersten Regel und das Einfügen eines zusätzlichen Attributselektors ohne :where(...)
würde daher die Spezifität erhöhen und dazu führen, dass color: blue
fälschlicherweise angewendet wird.
Die Pseudoklasse :where(...)
ist jedoch ziemlich neu. Für Browser, die diese Funktion nicht unterstützen, bietet der Polyfill eine sichere und einfache Behelfslösung: Sie können die Regeln absichtlich spezifizieren, indem Sie Ihren @container
-Regeln manuell einen Dummy-:not(.container-query-polyfill)
-Selektor hinzufügen:
@container (width > 300px) { .card { color: blue; } } .card { color: red; }
@container (width > 300px) { .card:not(.container-query-polyfill) { color: blue; } } .card { color: red; }
Dies hat eine Reihe von Vorteilen:
- Der Selektor im Quell-CSS hat sich geändert, sodass der Unterschied in der Spezifität explizit sichtbar ist. Dies dient auch als Dokumentation, damit Sie wissen, was betroffen ist, wenn Sie die Problemumgehung oder den Polyfill nicht mehr unterstützen müssen.
- Die Spezifität der Regeln bleibt immer gleich, da sie durch die Polyfill nicht geändert wird.
Während der Transpilierung ersetzt das Polyfill dieses Dummy-Element durch einen Attributselektor mit derselben Spezifität. Um Überraschungen zu vermeiden, verwendet der Polyfill beide Selektoren: Der ursprüngliche Quellenselektor wird verwendet, um zu bestimmen, ob das Element das Polyfill-Attribut erhalten soll, und der transpilierte Selektor wird für die Gestaltung verwendet.
Pseudoelemente
Eine Frage, die Sie sich stellen könnten, lautet: Wenn ein Polyfill ein cq-XYZ
-Attribut für ein Element festlegt, das die eindeutige Container-ID 123
enthält, wie können Pseudoelemente, für die keine Attribute festgelegt werden können, unterstützt werden?
Pseudoelemente sind immer an ein echtes Element im DOM gebunden, das als Ursprungselement bezeichnet wird. Während der Transpilierung wird stattdessen der bedingte Selector auf dieses echte Element angewendet:
@container (width > 300px) { #foo::before { /* ... */ } }
@media all { #foo:where([cq-XYZ~="123"])::before { /* ... */ } }
Anstatt in #foo::before:where([cq-XYZ~="123"])
umgewandelt zu werden (was ungültig wäre), wird der bedingte Selektor an das Ende des ursprünglichen Elements, #foo
, verschoben.
Das ist aber nicht alles, was wir brauchen. Ein Container darf keine Inhalte ändern, die nicht in einem Container enthalten sind (und ein Container kann nicht in sich selbst sein). Sie sollten jedoch berücksichtigen, dass genau das passiert, wenn #foo
selbst das abgefragte Containerelement wäre. Das Attribut #foo[cq-XYZ]
würde fälschlicherweise geändert und alle #foo
-Regeln werden fälschlicherweise angewendet.
Um dies zu korrigieren, verwendet der Polyfill tatsächlich zwei Attribute: eines, das nur von einem übergeordneten Element auf ein Element angewendet werden kann, und eines, das ein Element auf sich selbst anwenden kann. Das letztere Attribut wird für Selektoren verwendet, die auf Pseudoelemente ausgerichtet sind.
@container (width > 300px) { #foo, #foo::before { /* ... */ } }
@media all { #foo:where([cq-XYZ-A~="123"]), #foo:where([cq-XYZ-B~="123"])::before { /* ... */ } }
Da in einem Container niemals das erste Attribut (cq-XYZ-A
) auf sich selbst angewendet wird, stimmt der erste Selektor nur überein, wenn ein anderer übergeordneter Container die Containerbedingungen erfüllt und angewendet hat.
Relative Containereinheiten
Containerabfragen enthalten auch ein paar neue Einheiten, die Sie in Ihrem CSS verwenden können, z. B. cqw
und cqh
für 1% der Breite bzw. Höhe des nächstgelegenen passenden übergeordneten Containers. Dazu wird die Einheit mithilfe von benutzerdefinierten CSS-Eigenschaften in einen calc(...)
-Ausdruck umgewandelt. Der Polyfill legt die Werte für diese Eigenschaften über Inline-Stile für das Containerelement fest.
.card { width: 10cqw; height: 10cqh; }
.card { width: calc(10 * --cq-XYZ-cqw); height: calc(10 * --cq-XYZ-cqh); }
Es gibt auch logische Einheiten, wie cqi
und cqb
für die Inline-Größe bzw. die Blockgröße. Dies ist etwas komplizierter, da die Inline- und Blockachsen vom writing-mode
des Elements bestimmt werden, das die Einheit verwendet, und nicht vom abgefragten Element. Um dies zu unterstützen, wendet der Polyfill einen Inline-Stil auf jedes Element an, dessen writing-mode
sich von seinem übergeordneten Element unterscheidet.
/* Element with a horizontal writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqw);
--cq-XYZ-cqb: var(--cq-XYZ-cqh);
/* Element with a vertical writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqh);
--cq-XYZ-cqb: var(--cq-XYZ-cqw);
Jetzt können die Einheiten wie zuvor in die entsprechende benutzerdefinierte CSS-Eigenschaft umgewandelt werden.
Attribute
Containerabfragen fügen außerdem einige neue CSS-Eigenschaften wie container-type
und container-name
hinzu. Da APIs wie getComputedStyle(...)
nicht mit unbekannten oder ungültigen Eigenschaften verwendet werden können, werden diese nach dem Parsen auch in benutzerdefinierte CSS-Eigenschaften umgewandelt. Wenn eine Eigenschaft nicht geparst werden kann (z. B. weil sie einen ungültigen oder unbekannten Wert enthält), bleibt sie für den Browser unverändert.
.card { container-name: card-container; container-type: inline-size; }
.card { --cq-XYZ-container-name: card-container; --cq-XYZ-container-type: inline-size; }
Diese Eigenschaften werden immer dann transformiert, wenn sie gefunden werden, sodass der Polyfill mit anderen CSS-Funktionen wie @supports
kompatibel ist. Diese Funktionalität bildet die Grundlage der Best Practices für die Verwendung von Polyfill, wie unten beschrieben.
@supports (container-type: inline-size) { /* ... */ }
@supports (--cq-XYZ-container-type: inline-size) { /* ... */ }
Standardmäßig werden benutzerdefinierte CSS-Eigenschaften übernommen. Das bedeutet, dass beispielsweise jedes untergeordnete Element von .card
den Wert von --cq-XYZ-container-name
und --cq-XYZ-container-type
annimmt. So verhalten sich die nativen Properties definitiv nicht. Um dieses Problem zu lösen, fügt der Polyfill die folgende Regel vor allen Nutzerstilen ein. So wird sichergestellt, dass jedes Element die Anfangswerte erhält, es sei denn, es wird absichtlich durch eine andere Regel überschrieben.
* {
--cq-XYZ-container-name: none;
--cq-XYZ-container-type: normal;
}
Best Practices
Es ist zu erwarten, dass die meisten Besucher eher früher als später Browser mit integrierter Unterstützung für Containerabfragen ausführen. Dennoch ist es wichtig, die Nutzererfahrung für die verbleibenden Besucher zu optimieren.
Während des anfänglichen Ladevorgangs muss noch einiges geschehen, bevor der Polyfill die Seite formatieren kann:
- Der Polyfill muss geladen und initialisiert werden.
- Stylesheets müssen geparst und transpiliert werden. Da keine APIs für den Zugriff auf die Rohquelle eines externen Stylesheets vorhanden sind, muss sie möglicherweise asynchron erneut abgerufen werden, idealerweise jedoch nur aus dem Browser-Cache.
Wenn das Polyfill diese Bedenken nicht sorgfältig entkräftet, kann es zu einem Rückgang deiner Core Web Vitals kommen.
Damit du deinen Besuchern eine positive Erfahrung bieten kannst, wurden bei der Polyfill First Input Delay (FID) und Cumulative Layout Shift (CLS) priorisiert, was zu Kosten von Largest Contentful Paint (LCP) führen kann. Konkret garantiert der Polyfill nicht, dass Ihre Containerabfragen vor dem ersten Paint ausgewertet werden. Aus diesem Grund müssen Sie alle Inhalte, deren Größe oder Position sich durch Containerabfragen beeinflussen würde, so lange ausblenden, bis die Polyfill geladen und Ihr CSS-Code transpiliert wurde. Eine Möglichkeit dazu ist die Verwendung einer @supports
-Regel:
@supports not (container-type: inline-size) {
#content {
visibility: hidden;
}
}
Es wird empfohlen, dies mit einer reinen CSS-Ladeanimation zu kombinieren, die absolut über Ihrem (verborgenen) Content positioniert ist, um dem Besucher mitzuteilen, dass etwas passiert. Eine vollständige Demo dieses Ansatzes finden Sie hier.
Dieser Ansatz wird aus mehreren Gründen empfohlen:
- Ein reiner CSS-Loader minimiert den Aufwand für Nutzer mit neueren Browsern und bietet Nutzern mit älteren Browsern und langsameren Netzwerken ein einfaches Feedback.
- Durch die Kombination der absoluten Positionierung des Ladeprogramms mit
visibility: hidden
vermeiden Sie Layout Shifts. - Nachdem der Polyfill geladen wurde, wird die Bedingung
@supports
nicht mehr übergeben und deine Inhalte werden eingeblendet. - In Browsern mit integrierter Unterstützung für Containerabfragen wird die Bedingung nie erfüllt, sodass die Seite wie erwartet beim ersten Paint angezeigt wird.
Fazit
Wenn Sie Containerabfragen in älteren Browsern verwenden möchten, probieren Sie Polyfill aus. Falls Probleme auftreten sollten, können Sie das hier melden.
Wir sind gespannt, was Sie daraus machen werden.
Danksagungen
Hero-Image von Dan Cristian Pădureț auf Unsplash