Abschnitt 8 · Lektion 6
z-index und der Stacking Context
Sobald sich zwei Elemente überlappen, muss der Browser entscheiden, welches oben liegt. Er tut das nach festen Regeln, und die erste davon kommt ohne z-index aus.
Erst die Reihenfolge, dann die Position
<div class="feld">
<div class="kasten a">eins, steht zuerst im Markup</div>
<div class="kasten b">zwei</div>
<div class="kasten c">drei, steht zuletzt</div>
</div> .feld {
position: relative;
height: 220px;
font-family: system-ui, sans-serif;
}
/* Alle drei sind positioniert und haben keinen z-index.
Es entscheidet allein die Reihenfolge im Dokument. */
.kasten {
position: absolute;
width: 200px;
padding: 16px;
color: #ffffff;
}
.a {
top: 0;
left: 0;
background-color: #2563eb;
}
.b {
top: 40px;
left: 40px;
background-color: #15803d;
}
.c {
top: 80px;
left: 80px;
background-color: #b91c1c;
} Ohne weitere Angaben liegt oben, was später im Dokument steht. Im Beispiel sind alle drei Kästen gleich aufgebaut, und der letzte deckt die beiden anderen ab.
Dazu kommt eine zweite Regel. Positionierte Elemente liegen über nicht positionierten. Ein Element mit position: relative liegt also über einem statischen Nachbarn, selbst wenn dieser später im Markup steht.
Das erklärt schon einen guten Teil der Fälle, in denen etwas überraschend oben oder unten liegt. Bevor du zu z-index greifst, lohnt deshalb die Frage, ob nicht einfach die Reihenfolge im HTML die falsche ist.
z-index braucht eine Position
<div class="feld">
<div class="kasten statisch">z-index: 999, aber static</div>
<div class="kasten positioniert">ohne z-index, aber positioniert</div>
</div> .feld {
position: relative;
height: 180px;
font-family: system-ui, sans-serif;
}
.kasten {
width: 240px;
padding: 16px;
color: #ffffff;
}
/* Ein hoher z-index an einem statischen Element wirkt
nicht. Die Eigenschaft gilt nur für positionierte
Elemente sowie für Flex- und Grid-Kinder. */
.statisch {
z-index: 999;
background-color: #b91c1c;
}
/* Positionierte Elemente liegen ohnehin über
nicht positionierten. */
.positioniert {
position: relative;
top: -40px;
left: 40px;
background-color: #15803d;
} z-index nimmt eine ganze Zahl, auch negative. Es wirkt nur auf positionierte Elemente und auf die Kinder eines Flex- oder Grid-Containers, bei denen auch ohne position.
An einem statischen Element ist die Angabe wirkungslos. Sie ist gültig, sie wird nicht verworfen, und sie tut nichts.
Im Streifen siehst du das am Unterschied zwischen zwei Reitern. Am roten Kasten steht im Reiter „Regeln“ .statisch mit z-index: 999, nicht durchgestrichen und ohne Vermerk. Im Reiter „Werte“ gibt es dagegen gar keine z-index-Zeile, denn die steht dort nur bei Elementen, die auch positioniert sind. Am grünen Kasten daneben steht sie sehr wohl, mit dem Wert auto. Und dieser Kasten liegt oben, obwohl er gar keine Zahl hat.
Bewirkt ein z-index: 999 nichts, frage ich zuerst, ob das Element überhaupt positioniert ist. Die Antwort steht im Streifen in der Zeile position.
Zu den Zahlen selbst noch ein Wort. 999 und 9999 sind eine Kapitulation. Wer solche Werte vergibt, weiß nicht mehr, wogegen er anstinkt, und der Nächste muss dann 99999 nehmen. Eine kleine Skala mit drei bis vier Stufen, einmal oben in der Datei notiert, hält jahrelang.
Der Stacking Context
<div class="feld">
<div class="gruppe">
<div class="kind">z-index: 9999</div>
</div>
<div class="nachbar">z-index: 1</div>
</div>
<p>Nimm die Zeile mit opacity heraus, und das Kind liegt oben.</p> .feld {
position: relative;
height: 200px;
margin-bottom: 16px;
font-family: system-ui, sans-serif;
}
/* opacity unter 1 öffnet einen Stacking Context.
Alles darin wird zusammen als eine Ebene einsortiert,
und die hat selbst gar keinen z-index. */
.gruppe {
position: relative;
opacity: 0.99;
}
.kind {
position: absolute;
top: 0;
left: 0;
z-index: 9999;
width: 220px;
padding: 16px;
color: #ffffff;
background-color: #b91c1c;
}
.nachbar {
position: relative;
z-index: 1;
/* 20px und nicht mehr: .gruppe ist null Pixel hoch,
weil ihr einziges Kind absolut positioniert ist.
Der Nachbar startet deshalb ganz oben, und bei
einem größeren Wert überlappen die beiden
kaum noch. Zu sehen wäre dann nichts. */
top: 20px;
left: 60px;
width: 220px;
padding: 16px;
color: #ffffff;
background-color: #15803d;
} Bestimmte Eigenschaften öffnen einen Stacking Context, also einen eigenen kleinen Stapel. Alles darin wird untereinander sortiert und danach als ein einziges Element in den umgebenden Stapel einsortiert.
Was daraus folgt, siehst du im Beispiel. Das Kind hat z-index: 9999, der Nachbar draußen nur z-index: 1, und trotzdem liegt der Nachbar oben. Nach außen zählt nicht die 9999, sondern der Platz der Gruppe, und die hat selbst gar keinen z-index.
Nachlesen lässt sich das an beiden. Im Streifen steht am Kind z-index: 9999 und am Nachbarn z-index: 1. Beide Zahlen sind echt, sie treten nur nie gegeneinander an. Die Gruppe selbst lässt sich nicht antippen, sie ist nur eine Hülle ohne eigene Höhe. Ihre Regel steht trotzdem im Reiter „Regeln“ des Kindes, blass und mit „Selektor trifft nicht“, denn sie trifft die Gruppe und nicht das Kind. Darin steht opacity: 0.99, und diese Zeile öffnet den Stacking Context.
Diese Angaben öffnen unter anderem einen Stacking Context.
position: relativeoderabsolutezusammen mit einemz-indexungleichauto, der bekannte Fall. Ein Flex- oder Grid-Kind mitz-indexgehört auch dazu, auch ohneposition.position: fixedundsticky, immer, auch ganz ohnez-index. Das trifft die klebende Kopfzeile.opacityunter 1, der unbekannte Fall, und deshalb steht er im Beispiel. Machst du ein Element halb durchsichtig, ändert sich die Reihenfolge der Ebenen für alles darin.transformsamttranslate,rotateundscale, dazufilter,backdrop-filter,perspective,clip-path,mask,mix-blend-mode,containundisolation: isolate. Einwill-changegehört dazu, sobald es eine dieser Eigenschaften oderopacityankündigt.
transform samt Verwandten, filter, backdrop-filter, perspective und will-change: transform sind dieselben, mit denen in Lektion 8.4 ein fixiertes Element plötzlich mitscrollte. Beides hat denselben Grund. Ein solches Element macht ein eigenes Koordinatensystem und einen eigenen Stacking Context auf.
Deckungsgleich sind die beiden Listen aber nicht. Der Unterschied geht nur in eine Richtung. opacity, mix-blend-mode, clip-path, mask und isolation öffnen nur einen Stacking Context und machen keinen Bezugsrahmen für fixed. Umgekehrt öffnet alles, was einen Bezugsrahmen für ein fixiertes Element aufmacht, auch einen Stacking Context.
isolation: isolate als Werkzeug
Bisher klang der Stacking Context nach einem Ärgernis. Er ist aber ebenso oft die Lösung.
isolation: isolate öffnet einen Stacking Context, ohne sonst irgendetwas zu ändern. Keine Position, keine Transparenz, keine Verschiebung. Für genau diesen Zweck gibt es die Eigenschaft.
So lässt sich eine Komponente einkapseln. Die z-index-Werte in einer isolierten Kartenliste können niemals mit der Kopfzeile der Seite konkurrieren, ganz egal, welche Zahlen jemand später innerhalb einer einzelnen Karte vergibt. Die Liste als Ganzes hat einen Platz in der Reihenfolge, und alles Innere bleibt darin.
<div class="kopf">Kopfzeile, z-index: 5</div>
<ul class="liste">
<li class="karte">Tipptrainer <span class="badge">Neu</span></li>
</ul>
<div class="kopf">Kopfzeile, z-index: 5</div>
<ul class="liste isoliert">
<li class="karte">Tipptrainer <span class="badge">Neu</span></li>
</ul> body {
font-family: system-ui, sans-serif;
}
.kopf {
position: relative;
z-index: 5;
padding: 16px;
font-weight: 700;
color: #ffffff;
background-color: #18181b;
}
.liste {
margin: 8px 0 48px 0;
padding: 0;
list-style: none;
}
.karte {
position: relative;
padding: 16px;
border: 1px solid #d4d4d8;
border-radius: 12px;
}
/* Ragt 20px nach oben, also bis in die Kopfzeile. */
.badge {
position: absolute;
top: -20px;
right: 16px;
z-index: 10;
padding: 4px 8px;
font-size: 14px;
color: #ffffff;
background-color: #2563eb;
border-radius: 8px;
}
.isoliert {
isolation: isolate;
} Im Beispiel ragt das Badge einer Karte mit z-index: 10 in die Kopfzeile, die nur z-index: 5 hat. Im oberen Teil liegt es deshalb über ihr. Im unteren steht an der Liste isolation: isolate, und jetzt liegt die Kopfzeile oben, ohne dass sich an einer der beiden Zahlen etwas geändert hat.
Das ist der Unterschied zwischen einem Stylesheet, in dem die Zahlen mit der Zeit wachsen, und einem, in dem sie klein bleiben. Isolierte Komponenten brauchen nur 1, 2 und 3, weil sie nur gegen ihre eigenen Geschwister antreten. Nur die Zahlen der Seite selbst liegen weiter auseinander, etwa 10 und 100, damit dort später noch etwas dazwischen passt.
Wenn der z-index nicht wirkt
Liegt etwas nicht oben, obwohl der z-index hoch ist, gehe ich diese drei Fragen von oben nach unten durch.
- Ist das Element positioniert? Ohne Position wirkt
z-indexnicht. - Öffnet ein Vorfahre einen Stacking Context? Gesucht wird den Baum hinauf nach
opacity,transform,filterund den anderen aus der Liste. Die ersten beiden stehen im Reiter „Werte“, sobald sie gesetzt sind, alle anderen nur im Reiter „Regeln“. Gibt es so einen Vorfahren, ist das Element darin gefangen, und der Wert gehört an den Vorfahren. - Konkurrieren die beiden überhaupt im selben Stacking Context? Zwei Elemente in verschiedenen Stacking Contexts vergleichen ihre eigenen Zahlen nie miteinander. Es zählt der Platz ihrer jeweiligen Gruppen.
In der Aufgabe liegen vier Ebenen einander im Weg, und alle Positionen stehen schon.
Zum Mitnehmen
z-index ordnet nur innerhalb eines Stacking Context. Kommt ein Element trotz hoher Zahl nicht nach oben, hat meist ein Vorfahre einen eigenen geöffnet. Mit isolation: isolate öffnest du selbst einen, wenn eine Komponente unter sich bleiben soll.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
Was in dieser Lektion steckt
-
Artikel mit 4 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe im Editor, direkt im Browser geprüft
Öffnet sich mit dem Basis Konto.