CSS/Tutorials/Selektoren/Schachtelung

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

CSS kann viel Schreibaufwand sein. Wenn Sie eine HTML Baugruppe haben, die sich nur durch einen umständlichen Selektor korrekt identifizieren lässt, und Sie Styles für die Elemente innerhalb dieser Gruppe festlegen wollen, dann müssen Sie diesen identifizierenden Selektor vor jeder CSS Regel wiederholen, die sich auf das Innere dieser Gruppe bezieht.

CSS Präprozessoren helfen hier weiter. Sie ermöglichen das Formulieren von geschachtelten Regeln, bei denen der Selektor der äußeren Regel mit dem Selektor der jeweiligen inneren Regeln verknüpft wird. Auf diese Weise können Sie eine Art Namensraum erstellen, in dem Sie auf kompakte Weise CSS Regeln für das Innere einer HTML Elementgruppe notieren können.

Das CSS Nesting Modul, das 2023 eingeführt wurde, ermöglicht Ihnen solche Schachtelungen auch ohne CSS Präprozessor. Es gibt Gemeinsamkeiten, aber auch Unterschiede, die zu beachten sind.

Einführung

Als ein Beispiel, wie CSS ohne Schachtelung aussieht, sehen Sie hier einen vereinfachten Auszug aus den Selfhtml-Wiki Beispielen. Wir verwenden die Klasse note-box für unterschiedliche Infokästen. Beispiele werden durch die Klasse note-box-example noch zusätzlich formatiert. Innerhalb eines Beispiels gibt es div-Elemente für Überschrift, Beispielcode, Beispieltext etc, die durch ein Padding von 0.5em auf Abstand gehalten werden. Das div mit der Überschrift hat die Klasse note-box-title und braucht, weil es oben steht, kein padding-top, während das letzte div kein padding-bottom braucht.

CSS ohne Schachtelung
.note-box {
   outline: thin dotted hotpink;
}
.note-box-example {
   border-left: 5px solid #dfac20;
}
.note-box-example > div {
   padding: 0.5em 0;
}
.note-box-example .note-box-title {
   padding-top: 0;
}
.note-box-example > div:last-child {
   padding-bottom: 0;
}
Das HTML dazu sieht auszugsweise so aus:
<div class="note-box note-box-example">
   <div class="note-box-title">...</div>
   <div class="note-box-example-code">...</div>
</div>

Mit CSS Nesting lassen sich die Styles so schreiben:

CSS mit Schachtelung
.note-box {
   outline: thin dotted hotpink;
}
.note-box-example {
   border-left: 5px solid #dfac20;
   > div {                           /* Geschachtelte Regel */
      padding: 0.5em 0;
      &:last-child {                 /* Schachtelung in einer Schachtelung */
         padding-bottom: 0;
      }
   }
   .note-box-title {
      padding-top: 0;
   }
}

Durch die Schachtelung entsteht ein klarer Zusammenhang zwischen den CSS Regeln, der andernfalls nur durch direkte Nachbarschaft im Stylesheet sichtbar gemacht werden kann. Ein Vorteil für Entwickler ist, dass Editoren, die Code-Folding (Einklappen von Blöcken) unterstützten, nun alle CSS Regeln, die zu .note-box-example gehören, auf einen Klick ein- und ausblenden können, was in großen Stylesheets sehr zur Übersichtlichkeit beiträgt.

Der Unterschied zwischen Regeln auf der obersten Stufe und geschachtelten Regeln ist, dass der Selektor einer geschachtelten Regel als relativer Selektor interpretiert wird und deshalb mit einem Kombinator beginnt. Auch die Leerstelle ist ein Kombinator, deshalb ist auch der Selektor .note-box-title im obigen Beispiel relativ.

Die Schachtelung ist nicht auf eine Ebene begrenzt. Sie können in geschachtelte Regeln wiederum Regeln schachteln.

Beachten Sie: In der veröffentlichten Version 1 der Nesting Spezifikation ist festgelegt, dass ein solcher relativer Selektor nicht mit einem Typselektor beginnen darf, weil das den CSS Parser sonst zu sehr verkomplizieren würde. Gefordert wird, dass der Selektor einer geschachtelten Regel mit einem Kombinatorzeichen (also nicht dem Nachfahrenselektor) beginnt oder keinen einleitenden Typselektor hat.

Um die Einschränkungen, die diese Anforderung erzeugt, aufzuheben, wurde ein spezielles Selektorsymbol eingeführt: der Schachtelungsselektor &. Er hat mehrere Anwendungsfälle:

  • Der Selektor einer geschachtelten Regel beginnt mit einem Typselektor und soll per Nachfahrenselektor mit dem Selektor der Elternregel kombiniert werden.
    Beispiel: & div
    Diese Einschränkung existiert in der veröffentlichten W3C-Entwurfsversion der Spezifikation und ist im aktuellen Editor-Entwurf gestrichen. Firefox implementiert bereits den Editor-Entwurf, Chromium-Browser noch den veröffentlichten W3C-Entwurf (das soll sich mit Version 120 ändern).
  • Der Selektor einer geschachtelten Regel soll nicht durch einen Kombinator verbunden werden, sondern mit dem letzten Selektorteil des Elternselektors zu einem verbundenen Selektor zusammengefügt werden.
    Beispiel: &:last-child im obigen Beispiel, die :last-child-Pseudoklasse wird mit dem div des Elternselektors zu div:last-child verbunden
  • Der Elternselektor soll dem geschachtelten Selektor nicht vorangestellt werden, sondern an anderer Stelle eingefügt werden.
    Beispiel: body.editing & input
    Hier wird der Elternselektor zwischen body.editing und input gesetzt. Die geschachtelte Regel erweitert den Elternselektor an zwei Seiten.
  • Der Elternselektor soll im geschachtelten Selektor mehrfach verwendet werden

Unterschied zu textuellem Ersetzen

Der Unterschied zu Präprozessoren wie SASS oder LESS ist, dass diese blindlings Text ersetzen. Das CSS Nesting Modul tut das nicht, sondern verwendet die vom Parser erkannten CSS-Elemente (Klassen, IDs, etc). Der Selektor einer geschachtelten Regel muss deshalb für sich betrachtet syntaktisch korrekt sein. Die Folge ist zum Beispiel, dass .note-box-example nicht in .note-box geschachtelt werden kann, das Anhängen von -example an das & ändert den Klassenselektor der äußeren Regel nicht in .note-box-example ab. Statt dessen würde versucht, den einfachen Selektor .note-box durch einen weiteren Selektor -example zu einem verbundenen Selektor zu erweitern - was scheitert, weil -example keine gültige Selektorsyntax ist.

Nicht mit CSS Nesting möglich!
.note-box {
   outline: thin dotted hotpink;
   &-example {
      border-left: 5px solid #dfac20;
   }
}

Platzierung des & Selektors

Das & Zeichen muss nicht zwingend vorn im Selektor der inneren Regel stehen. Sie könnten zum Beispiel eine Regel schachteln, die zusätzliche Styles festlegt, wenn eine Beispielbox in einem aside-Element enthalten ist:

Schachteln mit zusätzlichem Elternelement
.note-box-example {
   border-left: 5px solid #dfac20;
   aside & {
      border-left-style: none;
      border-right: 5px solid #dfac20;
   }
}

Der Selektor der inneren Regel würde als aside .note-box-example interpretiert werden.

Wie schon erwähnt, ist es auch möglich, den Selektor der inneren Regel ohne Kombinator mit der äußeren Regel zu verbinden. Dabei gibt es Situationen, bei denen man aufpassen muss.

Erweitern eines verbundenen Selektors
.note-box {
   outline: thin dotted hotpink;
   &.note-box-example {
      border-left: 5px solid #dfac20;
   }
   figure& {
      margin: 0;
   }
}

Bei diesem Beispiel ist zu beachten, dass die Verbindung zweier Klassen in beliebiger Reihenfolge erfolgen kann, deshalb kann &.note-box-example oder .note-box-example& geschrieben werden, um eine Regel zu erstellen, die für Elemente mit beiden Klassen gilt. Enthält ein verbundener Selektor aber einen Elementselektor (hier für figure), so fordert die CSS Syntax, dass dieser immer als erstes stehen muss, weshalb es hier zwingend ist, figure& zu schreiben, wenn man auf ein <figure>-Element mit der Klasse note-box Bezug nehmen will.

Beachten Sie: Die Angaben figure& und figure & sind nicht gleichbedeutend. Die Leerstelle ist ein Nachfahrenkombinator. Deshalb selektiert die erste Version ein <figure>-Element mit der Klasse note-box, und die zweite Version selektiert ein Element mit der Klasse note-box, das Kindelement eines <figure>-Elements ist.

Die Folge dieser Einschränkung ist, dass Sie in einer Regel mit einem Selektor wie .foo .bar keine Regel schachteln können, die den Selektor zu .foo div.bar erweitern müsste. Sie können den Elternselektor nur vorn oder hinten erweitern.

Schachtelung und Spezifität

Für die Berechnung der Spezifität einer geschachtelten Regel müssen Sie sie so betrachten, als sei sie nicht geschachtelt worden, und als würde der Elternselektor in einer :is()-Pseudoklasse eingesetzt. Das bedeutet: die Spezifität des Elternselektors wird der Spezifität des Kindselektors hinzugefügt, und bei einer Selektorenliste im Elternselektor gelten die Regeln von :is()

Auswirkung der :is()-Auflösung - Version ohne :is()
<style>
#foo, p { color: red; }
#foo span, p span { color: green; }
.x, .y { color: blue; }
</style>
...
<p class="x">Hallo <span class="y">schöne</span> Welt!</p>

In dieser ersten Version ist der Text komplett blau. Die Spezifität des Klassenselektors ist höher als die des Elementselektors, deshalb hat die dritte Regel den Vorrang. Ändern wir nun die separat notierte Regel für das <span>-Element in einen geschachtelten Selektor ab:

Auswirkung der :is()-Auflösung - Version mit Schachtelung
<style>
#foo, p { 
  color: red;
  & span { color: green; }
}
.x, .y { color: blue; }
</style>
...
<p class="x">Hallo <span class="y">schöne</span> Welt!</p>

Das Wort "schöne" ist jetzt grün. Grund ist, dass der Selektor für die geschachtelte Regel als :is(#foo, p) span ausgewertet wird, und bei :is() ist es so, dass die Spezifität von :is() die Spezifität des spezifischsten Teilselektors ist, auch wenn dieser gar nicht zutrifft. Deswegen ist nun auf einmal ein ID-Selektor im Spiel, und color:green bekommt Vorrang.

Wenden wir :is() nun noch ausdrücklich auf den Selektor der äußeren Regel an:

Auswirkung der :is()-Auflösung - Version mit explizitem :is()
<style>
:is(#foo, p) { 
  color: red;
}
#foo span, p span { color: green; }
.x, .y { color: blue; }
</style>
...
<p class="x">Hallo <span class="y">schöne</span> Welt!</p>

Nun ist Hallo und Welt rot geworden, und schöne ist... blau?. Die Komma-Auflistung ohne :is() verwendet nur die Spezifität des zutreffenden Teilselektors, und p span sind zwei Elementselektoren, während .y als Klassenselektor spezifischer ist. Darum gewinnt blau. Warum nicht rot? Diese Regel hat doch die Spezifität des ID-Selektors? Das liegt daran, das eine direkt zutreffende Regel immer noch stärker ist als eine Vererbung.

Würden wir die Regel für span wieder schachteln, oder ihren Selektor als :is(#foo, p) schreiben, wäre wieder der Geister-ID-Selektor aktiv und schöne wäre grün.

Mehrfaches Setzen von &

Sie können Selektoren schreiben, die das & mehrfach enthalten:

Mehrfache Verwendung von &
.foo {
   color: red;

   & & { ... }                    /* Selektiert .foo .foo */
   .bar & .baz span& & { ... }    /* Selektiert .bar .foo .baz span.foo .foo */ 
}

Ob dies zu einem verständlichen Layout führt, darf bezweifelt werden. Aber es ist möglich.

Neufassung der Spezifikation: & kann entfallen

Die Notwendigkeit, den Selektor der Elternregel im Selektor der Kindregel durch das & zu repräsentieren, fand sich in der ursprünglichen Fassung der CSS Nesting Spezifikation. Eine neuere Fassung, die noch im Entwurfsstadium ist, gestattet es, das & wegzulassen, wenn es in dem Selektor der inneren Regel vorne steht und dahinter ein Kombinator folgt (die Leerstelle ist ebenfalls ein Kombinator!). Chromium-Browser vor Version 120 und die Safari-Browser unterstützen das zur Zeit nur mit einer Einschränkung: Der Selektor der inneren Regel darf keinen Typselektor enthalten.

CSS mit Schachtelung, vereinfachte Schreibweise
.note-box {
   outline: thin dotted hotpink;
}
.note-box-example {
   border-left: 5px solid #dfac20;
   > div {                           /* Kind-Kombinator, & ist unnötig */
      padding: 0.5em 0;
      &:last-child {                 /* Verbundener Selektor, & muss bleiben */
         padding-bottom: 0;
      }
   }
   .note-box-title {                 /* Nachfahrenkombinator, & ist unnötig */
      padding-top: 0;
   }
   & div.note-box-title {            /* Typselektor - Chromia vor v120 benötigen &, Webkits auch */
      padding-top: 0;
   }
   aside & {                         /* & steht nicht vorn, muss bleiben */
      ...
   }
}

Schachteln von @-Regeln

Außer Styleregeln können Sie auch @-Regeln in eine Styleregel schachteln. Nicht jede, aber alle, die ihrerseits Styleregeln mit Selektoren enthalten können. Das sind @media, @supports, @container, @layer und @scope.

Eine geschachtelte @-Regel kann einfach nur weitere CSS-Eigenschaften enthalten, Sie können aber auch Styleregeln darin schachteln.

Geschachtelte @media Regel
.note-box {
   @media screen {
      outline: thin dotted hotpink;
   }
   @media print {
      & .note-box-titel {
         border-bottom: thin solid black;
      }
   }
}

Dieses CSS würde einer .note-box nur bei der Bildschirmdarstellung einen rosafarbenen, gepunkteten Rand geben, und ein darin enthaltener .note-box-titel würde beim Druck eine untere Randline erhalten (das &-Zeichen wäre nach der neuen Spezifikation überflüssig).

Auch weitere Schachtelungen sind möglich:

Mehrfache Schachtelung von @media
article {
   margin: 0.5em 1em;
   @media (orientation:landscape) and (min-width:30em) {
      column-count: auto;
      column-width: 14em;
      @media (min-width: 60em) {
         column-gap: 2em;
         column-rule: thin solid black: 3;
      }
   }
}

Dieses Beispiel gibt article-Elementen zunächst nur einen Margin. Wenn die Anzeige im Querformat erfolgt und mindestens 30em Platz sind, wird eine mehrspaltige Darstellung aktiviert. Die innere @media-Regel fügt die Erweiterung hinzu, dass ab einer Breite von 60em ein Spaltenabstand von 2em mit einer Trennlinie darin verwendet werden soll. Ohne Schachtelung müsste das CSS so geschrieben werden:

CSS Aufbau ohne Schachtelung
article {
   margin: 0.5em 1em;
}
@media (orientation:landscape) and (min-width:30em) {
   article {
      column-count: auto;
      column-width: 14em;
   }
}
@media (orientation:landscape) and (min-width: 60em) {
   article {
      column-gap: 2em;
      column-rule: thin solid black: 3;
   }
}

Siehe auch

Quellen