CSS: z-index und sein Management in Projekten

Fast jeder Webentwickler weiß irgendetwas über die z-index-Eigenschaft aus dem CSS Standard. Wie schön wäre es, wenn alle Webentwickler alles über z-index wissen würden?

Ich weiß, W3C-Dokumente sind nicht jedermanns Sache. Deshalb habe ich für euch die offizielle Spezifikation gelesen und präsentiere hier die wichtigsten Fakten zum Thema.

Stapelreihenfolge (Stacking Order)

Beginnen wir zunächst mal ohne z-index und ohne Positionierung. Denn auch dann haben HTML-Elemente eine Hierarchie. Sehen wir uns direkt ein Beispiel dazu an:

See the Pen Natural Stacking Order by Markus Winkelmann (@designerzone) on CodePen.15823

Die Blockelemente (div) stehen eigentlich untereinander. Durch die Umpositionierung mit negativem margin (Außenabstand) erkennt man die Hierarchie gut, da sich die Elemente überlagern. Die Reihenfolge entspricht der Elementreihenfolge im Quelltext. Der Browser zeichnet den HTML-Code von oben nach unten, daher ist das letzte Element ganz oben auf dem Stapel.

z-index

Mit dem z-index kann man nun Einfluss auf die Stapelreihenfolge nehmen. Damit der Wert von z-index überhaupt vom Browser ausgewertet wird, muss das entsprechende Element positioniert sein. Das heißt seine CSS-Eigenschaft position darf nicht den Initialwert static aufweisen, sondern muss relative, absolute oder fixed sein.

Faustregel: z-index funktioniert nur zusammen mit position!

Stapelkontext (Stacking Context)

Der z-index nimmt nur ganzzahlige Werte an. Deshalb scheint es zunächst recht einleuchtend zu sein, was passiert, wenn man positionierte Ebenen neu anordnet. Elemente mit kleinerem z-index stehen hinter Elementen mit größerem z-index. Es ist leider etwas komplexer.

Beherzigt man obige Faustregel erzeugt man immer einen neuen Stapelkontext an dem Element, welches diese Eigenschaften erhält. Alle Kindelemente dieses Elements sind in dieser Ebene „gefangen“ und können nicht unter das Elternelement oder Vorfahren des Elternelements mit kleinerem oder gleichem z-index gestapelt werden. Hierzu ein Beispiel:

See the Pen z-index Stacking Order by Markus Winkelmann (@designerzone) on CodePen.15823

Manch einer erwartet in dem Beispiel vielleicht, dass das gelbe Quadrat aufgrund seines z-index-Wertes unterhalb des grauen Quadrats steht. Durch den neuen Stacking Context, der sich aus der Eltern-Kind-Beziehung der beiden Elemente und der absoluten Positionierung in Kombination mit dem z-index ergibt, ist dem nicht so. Hier ist z-index: 0; dann die unterste Ebene innerhalb dieses Kontextes.

Im Mozilla Developer Network gibt es eine umfassende Auflistung, welche Elemente einen Stapelkontext erzeugen. Neben Spezialfällen in bestimmten Browser-Device-Kombinationen und einigen eher selten genutzten CSS3-Features, fallen Eigenschaften auf, die schon mal öfter im Einsatz sind. Interessanterweise erzeugen auch opacity-Werte unter 1 und transform-Werte außer none einen Stapelkontext. 

Faustregel: Jedes Element kann unter gewissen Umständen einen Stacking Context bilden. Liegt ein Stacking Context vor, sind Kindelemente im jeweiligen Element an diese Ebene in der Hierarchie gebunden.

Wichtig ist, dass man verstanden hat, wie ein Stacking Context gebildet wird und was er für das Rendering im Browser bedeutet.

Stapelreihenfolge innerhalb eines Stapelkontextes 

In jedem Stacking Context werden die Ebenen in folgender Reihenfolge gezeichnet (von hinten nach vorne):

  1. Der Hintergrund und der Rahmen des den Stacking Context formenden Elements
  2. Elemente mit Stacking Context und negativem z-index (höchste Minuswerte zuerst)
  3. Alle im Textfluss befindlichen, nicht positionierten Block-Elemente
  4. Nicht positionierte floatende Elemente
  5. Alle im Textfluss befindlichen nicht positionierten Inline-Elemente (inklusive Inline-Tabellen und Inline-Block-Elementen)
  6. Elemente mit Stacking Context auf dem Level 0 (ggf. ohne z-index-Angabe)
  7. Elemente mit Stacking Context und z-index höher 0 (kleinste Werte zuerst)

Kinder von Elementen ohne Stapelkontext

Wenn ein Element Kinder besitzt, es selbst keinen Stapelkontext bildet, aber ein Kind eines Stapelkontextes ist, ist es möglich die Kinder dieses Elements hinter ihr Elternelement zu bringen. Beispiel:

See the Pen z-index Child under Parent by Markus Winkelmann (@designerzone) on CodePen.15823

Management von z-index

Sobald mehrere Personen an einem Projekt beteiligt sind und unterschiedliche Aspekte bearbeiten, kann es kompliziert werden den Überblick über alle z-index-Werte zu behalten. Da die Historie der Entwicklung oft nicht bekannt ist, kommen Fragen auf:

  • Warum hat das Element genau diesen z-index-Wert?
  • In welcher Ebene steht das Element genau?
  • Welche Auswirkung hätte eine Änderung des Wertes auf andere Elemente?
  • Welche z-index-Werte muss ich bei einer Änderung von einem Wert ebenfalls verändern?

Die Grundidee einer möglichen Lösung

Jackie Balzer schlägt vor die index-Funktion von SASS auszunutzen. Die Idee ist, eine Liste zu definieren mit den Elementen, die einen z-index-Wert erhalten sollen. Die Position in der Liste spiegelt die gewünschte Hierarchie auf der Website wider.

$elements: header, mega-dropdown, modals;

.mega-dropdown{
  z-index: index($elements, mega-dropdown);
}
.modals{
  z-index: index($elements, modals);
}
header{
  z-index: index($elements, header);
}

Die Liste wird dazu einfach in einer Variablen ($elements) gespeichert. In die Index-Funktion wird die Liste und das Element übergeben, dessen Position wir erfahren wollen. Der Rückgabewert ist dann die Positionsangabe als Number, beginnend bei 1. Als Ergebnis erhalten wir eine automatische Vergabe des z-index-Werts ab dem Wert 1.

.mega-dropdown{
  z-index: 2;
}
.modals{
  z-index: 3;
}
header{
  z-index: 1;
}

Wenn nun in einer späteren Entwicklungsphase zwischen header und mega-dropdown eine social-bar eingefügt werden soll, wird der große Vorteil dieses Ansatzes sichtbar. Es wird lediglich die Liste an der entsprechenden Stelle erweitert und alle betroffenen z-index-Werte werden automatisch angepasst.

Berücksichtigung von Stapelkontext-Hierarchien

Da der z-index-Wert innerhalb jedes Stapelkontextes bei 1 anfangen kann, könnte man für jedes Element eine Liste mit entsprechenden Unterelementen erzeugen. Dies sollten dann auch im HTML Kindelemente eines Containers sein.

Das bestehende Beispiel wird durch die social-bar und einer neuen Ebene (modal-elements) ergänzt.

$elements: header, social-bar, mega-dropdown, modals;
$modal-elements: form-controls, errors, autocomplete-list;

.modals{
  z-index: index($elements, modals);

  .form-control{
    z-index: index($modal-elements, form-controls);
  }
  .error{
    z-index: index($modal-elements, errors);
  }
  .autocomplete-list{
    z-index: index($modal-elements, autocomplete-list);
  }
}

Der SASS-Compiler wird so folgendes zusätzliche CSS ausspucken.

.modals{
  z-index: 4;
}
.modals .form-control{
  z-index: 1;
}
.modals .error{
  z-index: 2;
}
.modals .autocomplete-list{
  z-index: 3;
}

Faustregel: Das System funktioniert nur, wenn man sich konsequent daran hält und alle z-index-Werte über die Listen erzeugen lässt.

Was ist mit externen CSS-Quellen?

Verwendet man in seinem Projekt Standardlösungen (z. B. Layer, Dropdowns, etc.), die auch CSS nutzen, gibt es folgende Szenarien:

  • Das Drittanbieter CSS binde ich selbst ein!
    Füge das CSS als SCSS in deine SCSS Sourcen mit ein (z. B. in einem „vendor“-Ordner) und ersetze alle z-index-Vorkommen mit der z-Funktion.
  • Das Drittanbieter CSS liegt extern!
    1. Entweder erfolgt eine manuelle Sicherstellung, dass die festen z-index-Werte nicht mit den automatisch generierten Werten in Konflikt stehen, oder …
    2. … man fügt die z-index betroffenen Elemente in die Listen ein und überschreibt die Selektoren mit dem automatisch generierten z-index

Auslagerung und Fehlerbehandlung

Die Listen für alle Stapelkontexte sollten an einer zentralen Stelle gepflegt werden. Dafür bietet sich ein ähnlicher Ort an, wie er auch für globale Farbangaben genutzt wird. Zusätzlich könnte dort eine Hilfsfunktion definiert werden, die eine rudimentäre Fehlerbehandlung bietet.

$elements: header, mega-dropdown;

@function z($list, $element) {

   $z-index: index($list, $element);

   @if $z-index {
      @return $z-index;
   }

   @warn 'There is no item "#{$element}" in this list; choose one of: #{$list}';
   @return null;
}

.modals{
  z-index: z($elements, modals); 
  /* Wenn z null zurückgibt, wird z-index nicht ausgegeben! */
}

Die Variable der Liste sollte anstatt $elements vielleicht nach der Semantik des jeweiligen Stapelkontextes benannt werden. Was dafür passt, hängt dann immer vom Einzelfall ab. Gibt die Funktion null zurück, ignoriert der Kompiler die z-index-Eigenschaft komplett und gibt sie gar nicht erst aus.

Vielen Dank an Jackie Balzer für diese nützliche Funktion!

Quellen

Kommentar abgeben

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.