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

  • Chrome: 118. <ph type="x-smartling-placeholder">
  • Edge: 118. <ph type="x-smartling-placeholder">
  • Firefox: hinter einer Flagge.
  • Safari: 17.4. <ph type="x-smartling-placeholder">

Quelle

CSS-Selektoren zu schreiben

Wenn Sie Selektoren schreiben, werden Sie möglicherweise zwischen zwei Welten hin und her gerissen. Einerseits sollten Sie genau festlegen, welche Elemente Sie auswählen. Andererseits möchten Sie, dass die Selektoren einfach zu überschreiben und nicht eng mit der DOM-Struktur verknüpft sind.

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

  • Dieser Selektor hat eine ziemlich hohe Spezifität von (0,3,1), was es schwierig macht, ihn zu überschreiben, wenn Ihr Code wächst.
  • Durch die Verwendung des direkten untergeordneten Kombinators ist es eng mit der DOM-Struktur verknüpft. Sollte sich das Markup ändern, müssen Sie auch Ihr CSS ändern.

Sie sollten aber auch nicht nur img als Selektor für dieses Element schreiben, da dadurch alle Bildelemente auf Ihrer Seite ausgewählt werden.

Dabei ist es oft gar nicht so einfach, die richtige Balance zu finden. Im Laufe der Jahre haben einige Entwickler Lösungen und Behelfslösungen entwickelt, um euch in solchen Situationen zu helfen. Beispiel:

  • Methoden wie BEM schreiben vor, dass Sie diesem Element die Klasse card__img card__img--hero zuweisen, um die Spezifität gering zu halten, während Sie gleichzeitig eine spezifische Auswahl treffen können.
  • JavaScript-basierte Lösungen wie begrenzter CSS-Code oder Komponenten mit benutzerdefinierten Stilen schreiben alle Selektoren neu, indem den Selektoren zufällig generierte Strings wie sc-596d7e0e-4 hinzugefügt werden, damit sie nicht auf Elemente auf der anderen Seite der Seite ausgerichtet werden.
  • In einigen Bibliotheken werden Selektoren sogar gänzlich abgeschafft und es ist erforderlich, die Stiltrigger direkt im Markup selbst zu platzieren.

Aber was ist, wenn Sie keine davon brauchen? Was wäre, wenn Sie mit CSS sehr spezifisch angeben könnten, welche Elemente Sie auswählen, ohne dass Sie Selektoren mit hoher Spezifität oder eng mit Ihrem DOM gekoppelte Selektoren schreiben müssen? Hier kommt @scope ins Spiel und 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 Selektoren begrenzen. Dazu legen Sie den Bereichsstamm fest, der die Obergrenze der Unterstruktur bestimmt, auf die Sie ein Targeting vornehmen möchten. Mit einem Bereichs-Stammsatz können die enthaltenen Stilregeln – namens Bereichsstilregeln – nur aus dieser begrenzten Unterstruktur des DOMs auswählen.

Wenn Sie beispielsweise nur ein Targeting auf die <img>-Elemente in der Komponente „.card“ vornehmen möchten, legen Sie .card als Geltungsbereich der At-Regel für @scope fest.

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

Mit der auf einen Bereich reduzierten Stilregel img { … } können effektiv nur <img>-Elemente ausgewählt werden, die in den Geltungsbereich des übereinstimmenden .card-Elements fallen.

Wenn Sie verhindern möchten, dass die <img>-Elemente im Inhaltsbereich der Karte (.card__content) ausgewählt werden, können Sie den img-Selektor spezifischer gestalten. Sie können dies auch tun, indem Sie die Tatsache nutzen, dass die @scope-At-Regel auch ein Umfangslimit akzeptiert, das die Untergrenze bestimmt.

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

Diese auf einen Bereich reduzierte Stilregel zielt nur auf <img>-Elemente ab, die sich zwischen .card- und .card__content-Elementen in der Ancestor-Struktur befinden. Diese Art der Festlegung des Umfangs – mit einer Ober- und einer Untergrenze – wird häufig als Ringbereich bezeichnet.

Der :scope-Selektor

Standardmäßig sind alle Stilregeln auf einen Bereich relativ zum Gültigkeitsbereich. Es ist auch möglich, ein Targeting auf das Bereichs-Stammelement selbst vorzunehmen. Verwenden Sie dazu die Auswahl :scope.

@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 Sie möchten, können Sie das auch explizit angeben, indem Sie :scope selbst voranstellen. Alternativ können Sie den &-Selektor aus 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 */
    }
}

Bei einem Umfangslimit kann mit der Pseudoklasse :scope eine bestimmte Beziehung zum Umfangsstamm erforderlich sein:

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

Ein Umfangslimit kann auch auf Elemente außerhalb ihrer Gültigkeitsbasis mithilfe von :scope verweisen. Beispiel:

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

Beachten Sie, dass die auf einen Bereich reduzierten Stilregeln selbst die Unterstruktur nicht maskiert. Auswahlmöglichkeiten wie :scope + p sind ungültig, da versucht wird, Elemente auszuwählen, die nicht unter die Richtlinie fallen.

@scope und Spezifität

Die Selektoren, die Sie im Auftakt zu @scope verwenden, haben keinen Einfluss auf die Spezifität der enthaltenen Selektoren. Im folgenden Beispiel beträgt die Spezifität des img-Selektors weiterhin (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 & intern in den Selektor neu geschrieben, der für den Umfangsstamm verwendet wird und in einen :is()-Selektor eingebettet ist. Am Ende verwendet der Browser :is(#sidebar, .card) img als Selektor für den Abgleich. Dieser Vorgang 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 Spezifität des spezifischsten Arguments.

In diesem Beispiel ist die Spezifität von :is(#sidebar, .card) die 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

Neben Unterschieden bei der Berechnung der Spezifität besteht ein weiterer Unterschied zwischen :scope und & darin, dass :scope die übereinstimmende Gültigkeitsbasis darstellt, während & die Auswahl zum Abgleichen der bereichsbezogenen Wurzel darstellt.

Aus diesem Grund kann & mehrmals verwendet werden. Im Gegensatz dazu können Sie :scope nur einmal verwenden, da es keine Zuordnungsbasis innerhalb einer bereichsbezogenen Stammebene gibt.

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

Umfang ohne Vorlauf

Wenn Sie Inline-Styles mit dem <style>-Element schreiben, können Sie die Stilregeln auf das einschließende übergeordnete Element des <style>-Elements anwenden, indem Sie keinen Gültigkeitsstamm angeben. Lassen Sie dazu das Auftakt zu @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 zielen die Bereichsregeln nur auf Elemente innerhalb der div mit dem Klassennamen card__header ab, weil die div das übergeordnete Element des <style>-Elements ist.

@scope in der Kaskade

Innerhalb von CSS Cascade fügt @scope außerdem ein neues Kriterium hinzu: Bereichsnähe. Der Schritt erfolgt nach der Spezifität, aber vor der Erscheinungsreihenfolge.

Visualisierung der CSS-Kaskade.

Gemäß der Spezifikation:

Beim Vergleich von Deklarationen, die in Stilregeln mit unterschiedlichen Zugehörigkeitsstammen enthalten sind, wird die Deklaration mit den wenigsten Generations- oder gleichgeordneten Elementen zwischen dem bereichsbezogenen Stamm und dem Subjekt der Bereichsstilregel gewonnen.

Dieser neue Schritt ist nützlich, wenn Sie mehrere Varianten einer Komponente verschachteln möchten. In diesem 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>

Wenn Sie sich dieses Markup ansehen, lautet der dritte Link white anstelle von black, obwohl er ein untergeordnetes Element von div ist, auf das die Klasse .light angewendet wurde. Grund dafür ist die Reihenfolge der Kriterien, anhand derer die Kaskade hier den Sieger ermittelt. .dark a wurde als Letztes erklärt und gewinnt daher gegen die .light a-Regel.

Mit dem Kriterium für die Entfernung des Gültigkeitsbereichs ist dies nun gelöst:

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

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

Da die beiden bereichsbezogenen a-Selektoren dieselbe Spezifität haben, wird das Kriterium für die Umfangsnähe wirksam. Beide Selektoren werden nach der Nähe zu ihrer Zugehörigkeitsbasis gewichtet. Für dieses dritte a-Element ist es nur ein Sprung zum .light-Bereichsstamm, aber zwei zum .dark-Element. Daher hat der Selektor a in .light Vorrang.

Abschließender Hinweis: Selektorisolation, nicht Stilisolierung

Ein wichtiger Hinweis ist, dass @scope die Reichweite der Selektoren einschränkt. Es bietet keine Stilisolierung. Eigenschaften, bei denen die Vererbung bis auf die untergeordneten Elemente herabgesetzt wird, gilt nach wie vor über die Untergrenze von @scope hinaus. Eine solche Eigenschaft ist color. Wenn Sie diesen Bereich innerhalb eines Ringbereichs deklarieren, wird der color weiterhin für untergeordnete Elemente 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, weil sie den Wert von .card übernehmen.

(Titelbild von rustam burkhanov bei Unsplash)