Erste Schritte mit Stilabfragen

Die Möglichkeit, die Inline-Größe eines übergeordneten Elements und die Werte von Containerabfrageeinheiten abzufragen, wird seit Kurzem in allen modernen Browser-Engines stabil unterstützt.

Unterstützte Browser

  • Chrome: 105.
  • Edge: 105.
  • Firefox: 110.
  • Safari: 16.

Quelle

Die Begrenzungsspezifikation umfasst jedoch nicht nur Abfragen nach Größe, sondern auch nach Stilwerten eines übergeordneten Elements. Ab Chromium 111 können Sie Stilbegrenzungen für benutzerdefinierte Eigenschaftswerte anwenden und ein übergeordnetes Element nach dem Wert einer benutzerdefinierten Eigenschaft abfragen.

Unterstützte Browser

  • Chrome: 111.
  • Edge: 111.
  • Firefox: nicht unterstützt
  • Safari: 18.

Quelle

Das bedeutet, dass wir in CSS noch mehr logische Kontrolle über Stile haben und die Logik und die Datenschicht einer Anwendung besser von ihren Stilen trennen können.

Die CSS-Spezifikation für das Begrenzungsmodul der Ebene 3, die Abfragen zu Größe und Stil abdeckt, ermöglicht es, alle Stile von einem übergeordneten Element abzufragen, einschließlich Attribut- und Wertpaaren wie font-weight: 800. Bei der Einführung dieser Funktion funktionieren Stilabfragen derzeit jedoch nur mit benutzerdefinierten CSS-Eigenschaftswerten. Das ist immer noch sehr nützlich, um Stile zu kombinieren und Daten vom Design zu trennen. Sehen wir uns an, wie Sie Stilabfragen mit benutzerdefinierten CSS-Eigenschaften verwenden:

Erste Schritte mit Stilabfragen

Angenommen, wir haben folgenden HTML-Code:

<ul class="card-list">
  <li class="card-container">
    <div class="card">
      ...
    </div>
  </li>
</ul>

Wenn Sie Stilabfragen verwenden möchten, müssen Sie zuerst ein Containerelement einrichten. Je nachdem, ob Sie ein direktes oder indirektes übergeordnetes Element abfragen, ist ein etwas anderer Ansatz erforderlich.

Direkte übergeordnete Elemente abfragen

Diagramm einer Stilabfrage.

Anders als bei Stilabfragen müssen Sie keine Begrenzung mit der Eigenschaft container-type oder container auf .card-container anwenden, damit .card die Stile des direkt übergeordneten Elements abfragen kann. Wir müssen die Stile (in diesem Fall Werte für benutzerdefinierte Properties) jedoch auf einen Container (in diesem Fall .card-container) oder ein Element anwenden, das das Element enthält, für das wir im DOM ein Styling vornehmen. Wir können die Stile, die wir abfragen, nicht auf das direkte Element anwenden, das wir mit dieser Abfrage stylen, da dies zu einer unendlichen Schleife führen kann.

Wenn Sie ein übergeordnetes Element direkt abfragen möchten, können Sie Folgendes eingeben:

/* styling .card based on the value of --theme on .card-container */
@container style(--theme: warm) {
  .card {
    background-color: wheat;
    border-color: brown; 
    ...
  }
}

Sie haben vielleicht bemerkt, dass die Stilabfrage die Abfrage in style() einschließt. Damit lassen sich Größenwerte von Stilen unterscheiden. Sie können beispielsweise eine Abfrage nach der Breite des Containers als @container (min-width: 200px) { … } schreiben. Die Stile werden angewendet, wenn der übergeordnete Container mindestens 200 Pixel breit ist. min-width kann jedoch auch eine CSS-Eigenschaft sein und der CSS-Wert von min-width lässt sich mithilfe von Stilabfragen abfragen. Deshalb sollten Sie den style()-Wrapper verwenden, um den Unterschied deutlich zu machen: @container style(min-width: 200px) { … }.

Indirekte übergeordnete Elemente stylen

Wenn Sie Stile für ein Element abfragen möchten, das kein direktes übergeordnetes Element ist, müssen Sie diesem Element ein container-name zuweisen. So können wir beispielsweise Stile auf .card basierend auf den Stilen von .card-list anwenden, indem wir .card-list eine container-name zuweisen und in der Stilabfrage darauf verweisen.

/* styling .card based on the value of --moreGlobalVar on .card-list */
@container cards style(--moreGlobalVar: value) {
  .card {
    ...
  }
}

Es ist im Allgemeinen eine Best Practice, Ihren Containern Namen zu geben, damit klar ist, was Sie abfragen, und damit Sie einfacher auf diese Container zugreifen können. Ein Beispiel hierfür ist, wenn Sie Elemente innerhalb von .card direkt gestalten möchten. Ohne einen benannten Container in .card-container können sie ihn nicht direkt abfragen.

In der Praxis macht das aber viel mehr Sinn. Sehen wir uns einige Beispiele an:

Stilabfragen in der Praxis

Demobild mit mehreren Produktkarten, einige mit den Tags „Neu“ oder „Begrenzte Stückzahl“ und die Karte „Begrenzte Stückzahl“ mit einem roten Hintergrund.

Stilabfragen sind besonders nützlich, wenn Sie entweder über eine wiederverwendbare Komponente mit mehreren Varianten verfügen oder wenn Sie nicht die Kontrolle über alle Stile haben, aber in bestimmten Fällen Änderungen anwenden müssen. Dieses Beispiel zeigt eine Reihe von Produktkarten mit derselben Kartenkomponente. Einige Produktkarten enthalten zusätzliche Details/Hinweise wie „Neu“ oder „Auf Lager“, die durch eine benutzerdefinierte Eigenschaft namens --detail ausgelöst werden. Wenn ein Produkt nur noch „wenig auf Lager“ ist, wird außerdem ein dunkelroter Rahmen angezeigt. Diese Art von Informationen wird wahrscheinlich auf dem Server gerendert und kann über Inline-Styles auf die Karten angewendet werden, z. B. so:

 <div class="product-list">
  <div class="product-card-container" style="--detail: new">
    <div class="product-card">
      <div class="media">
        <img .../>
      <div class="comment-block"></div>
    </div>
  </div>
  <div class="meta">
    ...
  </div>
  </div>
  <div class="product-card-container" style="--detail: low-stock">
    ...
  </div>
  <div class="product-card-container">
    ...
  </div>
  ...
</div>

Aufgrund dieser strukturierten Daten können Sie Werte an --detail übergeben und diese benutzerdefinierte CSS-Eigenschaft zum Anwenden der Stile verwenden:

@container style(--detail: new) {
  .comment-block {
    display: block;
  }
  
  .comment-block::after {
    content: 'New';
    border: 1px solid currentColor;
    background: white;
    ...
  }
}

@container style(--detail: low-stock) {
  .comment-block {
    display: block;
  }
  
  .comment-block::after {
    content: 'Low Stock';
    border: 1px solid currentColor;
    background: white;
    ...
  }
  
  .media-img {
    border: 2px solid brickred;
  }
}

Mit dem Code oben können wir einen Chip für --detail: low-stock und --detail: new anwenden. Möglicherweise ist der Codeblock aber redundant. Derzeit gibt es keine Möglichkeit, nur nach der Anwesenheit von --detail mit @container style(--detail) zu fragen. Das würde eine bessere Weitergabe von Stilen und weniger Wiederholungen ermöglichen. Diese Funktion wird derzeit in der Arbeitsgruppe diskutiert.

Wetterkarten

Im vorherigen Beispiel wurde eine einzelne benutzerdefinierte Property mit mehreren möglichen Werten verwendet, um Stile anzuwenden. Sie können aber auch mehrere benutzerdefinierte Properties verwenden und abfragen. Hier ein Beispiel für die Wetterkarte:

Demo der Wetterkarten

Um die Farbverläufe und Symbole für den Hintergrund dieser Karten zu gestalten, suchen Sie nach Wettermerkmalen wie „bewölkt“, „regnerisch“ oder „sonnig“:

@container style(--sunny: true) {
  .weather-card {
    background: linear-gradient(-30deg, yellow, orange);
  }
  
  .weather-card:after {
    content: url(<data-uri-for-demo-brevity>);
    background: gold;
  }
}

So können Sie jede Karte anhand ihrer individuellen Merkmale gestalten. Sie können aber auch Stile für Kombinationen von Merkmalen (benutzerdefinierte Eigenschaften) festlegen, indem Sie den Kombinator and auf die gleiche Weise wie bei Medienabfragen verwenden. Ein Tag, der sowohl bewölkt als auch sonnig ist, würde beispielsweise so aussehen:

@container style(--sunny: true) and style(--cloudy: true) {
    .weather-card {
      background: linear-gradient(24deg, pink, violet);
    }
  
  .weather-card:after {
      content: url(<data-uri-for-demo-brevity>);
      background: violet;
  }
}

Daten vom Design trennen

In beiden Demos besteht ein struktureller Vorteil darin, die Datenebene (das DOM, das auf der Seite gerendert wird) von den angewendeten Stilen zu trennen. Die Stile werden als mögliche Varianten geschrieben, die im Komponentenstil vorhanden sind, während ein Endpunkt die Daten senden könnte, die dann zum Stilisieren der Komponente verwendet werden. Sie können einen einzelnen Wert verwenden, z. B. im ersten Fall, indem Sie den --detail-Wert aktualisieren, oder mehrere Variablen, z. B. im zweiten Fall, indem Sie entweder --rainy oder --cloudy oder --sunny festlegen. Und das Beste ist, dass Sie diese Werte auch kombinieren können. Wenn Sie sowohl --sunny als auch --cloudy prüfen, könnte der Stil teilweise bewölkt sein.

Die Aktualisierung von benutzerdefinierten Property-Werten über JavaScript kann nahtlos erfolgen, entweder beim Einrichten des DOM-Modells (d. h. beim Erstellen der Komponente in einem Framework) oder jederzeit mit <parentElem>.style.setProperty('--myProperty’, <value>). I

In dieser Demo wird mit wenigen Codezeilen der --theme einer Schaltfläche aktualisiert und Stile mithilfe von Stilabfragen und dieser benutzerdefinierten Property (--theme) angewendet:

Gestalten Sie die Karte mithilfe von Stilabfragen. Das JavaScript, das zum Aktualisieren der Werte der benutzerdefinierten Eigenschaften verwendet wird, lautet wie folgt:

const themePicker = document.querySelector('#theme-picker')
const btnParent = document.querySelector('.btn-section');

themePicker.addEventListener('input', (e) => {
  btnParent.style.setProperty('--theme', e.target.value);
})

Die in diesem Artikel beschriebenen Funktionen sind erst der Anfang. Mit Containerabfragen können Sie dynamische, responsive Oberflächen erstellen. Insbesondere bei Stilabfragen gibt es immer noch einige offene Probleme. Eine davon ist die Implementierung von Stilabfragen für CSS-Stile, die über benutzerdefinierte Eigenschaften hinausgehen. Diese Funktion ist bereits Teil der aktuellen Spezifikationsebene, wurde aber noch in keinem Browser implementiert. Die boolesche Kontextbewertung wird der aktuellen Spezifikationsebene hinzugefügt, wenn das ausstehende Problem behoben ist. Eine Bereichsabfrage ist für die nächste Ebene der Spezifikation geplant.