Begrenzen Sie die Reichweite Ihrer Selectors mit der CSS-„@scope at-rule“

Hier erfahren Sie, wie Sie mit @scope Elemente nur innerhalb einer begrenzten Unterstruktur Ihres DOMs auswählen.

Unterstützte Browser

  • 118
  • 118
  • x
  • x

CSS-Selektoren auf einfache Weise schreiben

Beim Schreiben von Selectors kann es sein, dass Sie zwischen zwei Welten hin und her rutschen. Auf der einen Seite möchten Sie ziemlich genau angeben, welche Elemente Sie auswählen. Andererseits möchten Sie, dass Ihre Selektoren einfach zu überschreiben sind und nicht eng an die DOM-Struktur gekoppelt sind.

Wenn Sie beispielsweise „das Hero-Image im Inhaltsbereich der Kartenkomponente“ auswählen möchten – eine ziemlich spezifische Elementauswahl – sollten Sie höchstwahrscheinlich keinen Selector wie .card > .content > img.hero schreiben.

  • Dieser Selektor hat eine ziemlich hohe Spezifität von (0,3,1), was ein Überschreiben erschwert, wenn der Code wächst.
  • Durch den direkten untergeordneten Kombinator ist er eng an die DOM-Struktur gekoppelt. Sollte sich das Markup irgendwann ändern, musst du auch deinen CSS-Code ändern.

Außerdem sollten Sie nicht nur img als Selektor für dieses Element schreiben, da sonst alle Bildelemente auf der Seite ausgewählt werden.

Dabei ist es oft eine Herausforderung, die richtige Balance zu finden. Im Laufe der Jahre haben einige Entwickler Lösungen und Behelfslösungen entwickelt, um Sie in solchen Situationen zu unterstützen. Beispiel:

  • Methoden wie BEM schreiben vor, dass Sie diesem Element eine Klasse von card__img card__img--hero zuweisen, um die Spezifität niedrig zu halten und gleichzeitig eine spezifische Auswahl zu ermöglichen.
  • Bei JavaScript-basierten Lösungen wie Bereichsbezogenem CSS oder Styled Components werden alle Selektoren neu geschrieben, indem zufällig generierte Strings wie sc-596d7e0e-4 in die Selektoren eingefügt werden, um zu verhindern, dass sie auf Elemente am anderen Rand der Seite ausgerichtet werden.
  • Bei einigen Bibliotheken werden Selectors sogar vollständig abgeschafft und Sie müssen die Stiltrigger direkt in das Markup selbst einfügen.

Aber was ist, wenn Sie keines davon brauchen? Was wäre, wenn Sie bei CSS die Möglichkeit hätten, die von Ihnen ausgewählten Elemente ziemlich genau anzugeben, ohne dass Sie Selektoren mit hoher Spezifität oder solche, die eng an Ihr DOM gekoppelt sind, schreiben müssen? Hier kommt @scope ins Spiel. Es bietet Ihnen die Möglichkeit, Elemente nur innerhalb einer Unterstruktur Ihres DOMs auszuwählen.

Jetzt neu: @scope

Mit @scope können Sie die Reichweite Ihrer Selectors einschränken. Dazu legen Sie die Umfangswurzel fest, die die Obergrenze der Unterstruktur bestimmt, auf die Sie ein Targeting vornehmen möchten. Bei einem Umfangsstammsatz können die enthaltenen Stilregeln – namens Bereichsbezogene Stilregeln – nur aus dieser eingeschränkten Unterstruktur des DOMs ausgewählt werden.

Wenn Sie beispielsweise nur die <img>-Elemente in der .card-Komponente für das Targeting verwenden möchten, legen Sie .card als Umfangsstammverzeichnis der @scope-At-Regel fest.

@scope (.card) {
    img {
        border-color: green;
    }
}

Mit der beschränkten Stilregel img { … } können nur <img>-Elemente ausgewählt werden, die sich im Geltungsbereich des übereinstimmenden .card-Elements befinden.

Um zu verhindern, dass die <img>-Elemente im Inhaltsbereich der Karte (.card__content) ausgewählt werden, kannst du die img-Auswahl spezifischer gestalten. Eine andere Möglichkeit besteht darin, die Tatsache zu nutzen, dass die at-Regel @scope auch ein Bereichslimit akzeptiert, das die Untergrenze bestimmt.

@scope (.card) to (.card__content) {
    img {
        border-color: green;
    }
}

Diese beschränkte Stilregel gilt nur für <img>-Elemente, die in der Ancestor-Baumstruktur zwischen .card- und .card__content-Elementen platziert werden. Diese Art des Umfangs – mit einer Ober- und Untergrenze – wird oft als Ringdiagramm bezeichnet.

Die :scope-Auswahl

Standardmäßig sind alle auf einen Bereich reduzierten Stilregeln relativ zum Stammverzeichnis für den Umfang. Sie können auch ein Targeting auf das den Umfang festlegende Stammelement selbst festlegen. Verwenden Sie dazu die :scope-Auswahl.

@scope (.card) {
    :scope {
        /* Selects the matched .card itself */
    }
    img {
       /* Selects img elements that are a child of .card */
    }
}

Selektoren in auf einen Bereich reduzierten Stilregeln wird implizit :scope vorangestellt. Wenn du möchtest, kannst du dies explizit mitteilen, indem du selbst :scope voranstellst. Alternativ können Sie den &-Selektor aus der CSS-Verschachtelung voranstellen.

@scope (.card) {
    img {
       /* Selects img elements that are a child of .card */
    }
    :scope img {
        /* Also selects img elements that are a child of .card */
    }
    & img {
        /* Also selects img elements that are a child of .card */
    }
}

Für ein Bereichslimit kann die Pseudoklasse :scope verwendet werden, um eine bestimmte Beziehung zur Zugehörigkeitsstammbasis zu verlangen:

/* .content is only a limit when it is a direct child of the :scope */
@scope (.media-object) to (:scope > .content) { ... }

Ein Bereichslimit kann mithilfe von :scope auch auf Elemente außerhalb ihres Bereichs verweisen. Beispiel:

/* .content is only a limit when the :scope is inside .sidebar */
@scope (.media-object) to (.sidebar :scope .content) { ... }

Die Unterstruktur für die auf einen Bereich reduzierten Stilregeln selbst kann nicht maskiert werden. Auswahlen wie „:scope + p“ sind ungültig, da versucht wird, Elemente auszuwählen, die nicht in den Gültigkeitsbereich fallen.

@scope und Spezifität

Die Selectors, die Sie im Vorfeld für @scope verwenden, haben keinen Einfluss auf die Spezifität der enthaltenen Selectors. Im Beispiel unten beträgt die Spezifität des img-Selektors immer noch (0,0,1).

@scope (#sidebar) {
    img { /* Specificity = (0,0,1) */
        …
    }
}

Die Spezifität von :scope ist die einer regulären Pseudoklasse, nämlich (0,1,0).

@scope (#sidebar) {
    :scope img { /* Specificity = (0,1,0) + (0,0,1) = (0,1,1) */
        …
    }
}

Im folgenden Beispiel wird der & intern in den Selektor umgeschrieben, der für den Umfangsstamm verwendet wird und in einen :is()-Selektor eingebettet ist. Abschließend verwendet der Browser :is(#sidebar, .card) img als Selektor für den Abgleich. Dieses Verfahren wird als Entzuckerung bezeichnet.

@scope (#sidebar, .card) {
    & img { /* desugars to `:is(#sidebar, .card) img` */
        …
    }
}

Da & mithilfe von :is() berechnet wird, wird die Spezifität von & gemäß den :is()-Spezifitätsregeln berechnet: Die Spezifität von & entspricht der des spezifischsten Arguments.

In diesem Beispiel ist :is(#sidebar, .card) die Spezifität des spezifischsten Arguments, nämlich #sidebar, und wird daher zu (1,0,0). Wenn Sie das mit der Spezifität von img(0,0,1) – kombinieren, erhalten Sie (1,0,1) als Spezifität für den gesamten komplexen Selektor.

@scope (#sidebar, .card) {
    & img { /* Specificity = (1,0,0) + (0,0,1) = (1,0,1) */
        …
    }
}

Die Differenz zwischen :scope und & innerhalb von @scope

Abgesehen von Unterschieden bei der Berechnung der Spezifität besteht ein weiterer Unterschied zwischen :scope und & darin, dass :scope die übereinstimmende Wurzel des Geltungsbereichs darstellt, während & den Selektor darstellt, der zum Abgleichen der Zugehörigkeitswurzel verwendet wird.

Daher kann & mehrmals verwendet werden. Das steht im Gegensatz zu :scope, das Sie nur einmal verwenden können, da Sie keine Zugehörigkeitsstammwurzel innerhalb einer Zugehörigkeitsstammdomain abgleichen können.

@scope (.card) {
  & & { /* Selects a `.card` in the matched root .card */
  }
  :root :root { /* ❌ Does not work */
    …
  }
}

Bereich ohne Vorankündigung

Wenn Sie Inline-Styles mit dem <style>-Element schreiben, können Sie die Stilregeln auf das einschließende übergeordnete Element des <style>-Elements beschränken, indem Sie keinen zugehörigen Stamm angeben. Dazu lassen Sie das Auftakt der @scope weg.

<div class="card">
  <div class="card__header">
    <style>
      @scope {
        img {
          border-color: green;
        }
      }
    </style>
    <h1>Card Title</h1>
    <img src="…" height="32" class="hero">
  </div>
  <div class="card__content">
    <p><img src="…" height="32"></p>
  </div>
</div>

Im Beispiel oben werden die auf einen Bereich reduzierten Regeln nur auf Elemente innerhalb der div mit dem Klassennamen card__header ausgerichtet, weil div das übergeordnete Element des <style>-Elements ist.

@scope in der Kaskade

Innerhalb der CSS-Cascade fügt @scope ein neues Kriterium hinzu: Umfangsnähe. Der Schritt kommt nach der Spezifität, aber vor der Reihenfolge des Erscheinens.

Visualisierung der CSS Cascade

Gemäß der Spezifikation:

Wenn Sie Deklarationen vergleichen, die in Stilregeln mit verschiedenen Wurzeln für den Umfang enthalten sind, gewinnt die Deklaration mit den wenigsten Hops auf Generationen oder gleichgeordnete Elemente zwischen dem Stammelement des Bereichs und dem Geltungsbereich der Stilregel.

Dieser neue Schritt ist praktisch, wenn Sie mehrere Varianten einer Komponente verschachteln möchten. Im folgenden Beispiel wird @scope noch nicht verwendet:

<style>
    .light { background: #ccc; }
    .dark  { background: #333; }
    .light a { color: black; }
    .dark a { color: white; }
</style>
<div class="light">
    <p><a href="#">What color am I?</a></p>
    <div class="dark">
        <p><a href="#">What about me?</a></p>
        <div class="light">
            <p><a href="#">Am I the same as the first?</a></p>
        </div>
    </div>
</div>

Bei der Ansicht dieses kleinen Markups lautet der dritte Link white statt black, obwohl er einer div untergeordnet ist, auf die die Klasse .light angewendet wird. Dies ist auf das Kriterium für die Darstellung in der Kaskade zurückzuführen, das hier zur Bestimmung des Siegers verwendet wird. Das System erkennt, dass .dark a als Letztes deklariert wurde und daher aufgrund der .light a-Regel gewinnt

Mit dem Umfangskriterium für die Nähe wurde dieses Problem nun behoben:

@scope (.light) {
    :scope { background: #ccc; }
    a { color: black;}
}

@scope (.dark) {
    :scope { background: #333; }
    a { color: white; }
}

Da beide auf einen Bereich reduzierten a-Selektoren dieselbe Spezifität haben, wird das Bereichs-Näherungskriterium wirksam. Beide Selectors werden nach Nähe zu ihrer Zugehörigkeitswurzel gewichtet. Für das dritte a-Element ist es nur ein Hop zum Bereichsstamm .light und zwei Hops auf den .dark-Bereich. Daher hat die Auswahl a in .light Vorrang.

Abschlusshinweis: Selektor-, nicht Stil-Isolierung

Wichtiger Hinweis: @scope schränkt die Reichweite der Selektoren ein und bietet keine Stilisolation. Properties, die von untergeordneten Elementen übernommen werden, übernehmen weiterhin die Untergrenze von @scope. Eines dieser Attribute ist color. Wenn Sie diesen Wert innerhalb eines Ringbereichs deklarieren, wird der color trotzdem von den untergeordneten Elementen innerhalb des Lochs des Rings übernommen.

@scope (.card) to (.card__content) {
  :scope {
    color: hotpink;
  }
}

Im Beispiel oben haben das .card__content-Element und seine untergeordneten Elemente die Farbe hotpink, da sie den Wert von .card übernehmen.

(Titelbild von rustam burkhanov auf Unsplash)