Kategoria:CSS

Dlaczego z-index nie działa?

  • Czas potrzebny na przeczytanie:6 minut
  • Opublikowane:

Niech pierwszy rzuci kamieniem ten, który nigdy nie miał problemów z z-index w CSS. Poprawne ułożenie elementów nie raz przyprawiło mnie o ból głowy. Masakra! Nic w tym dziwnego, ten mechanizm to jeden z "kruczków" CSS, które potrafią być naprawdę problematyczne.

Tworzysz prosty sklep e-commerce. Chciałbyś, żeby nawigacja była pod, a koszyk nad innymi elementami. Prościzna? Dopóki wszystko będzie szło po Twojej myśli. Zanim zaczniesz wyrywać włosy z głowy, z powodu niedziałającego z-index: 999999;, zrozum jak ten mechanizm naprawdę działa.

Podstawy

Wszyscy wiemy jak ustawić dwa kwadraty tak, aby jeden nachodził na drugi. Wrzucamy dwa boxy do wspólnego rodzica. Dominującemu kwadratowi nadajemy większą wartość z-index:

<style>
  .wrapper {
    position: relative;
  }

  .box-red {
    position: absolute;
    z-index: 2;
  }

  .box-pink {
    position: absolute;
    z-index: 1;
  }
</style>

<div class="wrapper">
  <div class="box-red"></div>
  <div class="box-pink"></div>
</div>

Śmiga, można się rozejść. Rozwiązaliśmy problem upierdliwych z-index!

Hola hola, to jeszcze nie wszystko. Weźmy teraz na tapet sytuację, w której nasz dominujący kwadrat, opakowujemy w dodatkowy wrapper:

Wszystko poszło się je... Wróćmy do kodu i przeanalizujmy co mogło pójść nie tak.

<div class="wrapper">
  <div class="box-dashed">
    <div class="box-red"></div>
  </div>
  <div class="box-pink"></div>
</div>

Na pierwszy ogień idzie HTML - od teraz nasz dominujący kwadrat jest opleciony w dodatkowego rodzica. Z perspektywy CSS, nasz rodzic co prawda ma mniejszy z-index od kwadratu box-pink, ale dominujący box-red powinien to przebić.

Gdzie więc leży problem? 🤔

.wrapper {
  position: relative;
}

.box-dashed {
  position: relative;
  z-index: 1;
}

.box-red {
  position: absolute;
  z-index: 3;
}

.box-pink {
  position: absolute;
  z-index: 2;
}

Podpowiedź: ustawienie z-index na 999999999 nie pomoże 🙈

Stacking Context

Winowajcą tego całego zamieszania jest mistyczny mechanizm w CSS zwany stacking context.

Domyślnie, czysty dokument HTML tworzy context względem którego układa elementy. O stacking context możemy myśleć jak o warstwie, która pozwala nam układać elemeny względem siebie.

Poza domyślą warstwą, nadając elementom konkretne właściwości, tworzymy nowy stacking context. Dla przykładu - połączenie position: relative / position: absolute + z-index tworzy nową warstwę.

Porównywać ze sobą elementy możemy tylko względem nadrzędnej warstwy. Właściwość z-index nie jest właściwością globalną!

Tworząc dwa elementy, gdzie każdy element posiada position: relative + odpowiedni z-index, tworzymy dwie warstwy. Obie warstwy posiadają w sobie dodatkowo po trzy elementy. Każdy z zagnieżdżonych elementów posiada własny z-index + position: absolute.

Wizualizując sobie ten mechanizm, możemy myśleć o zagnieżdżonych warstwach, jak o wersjach nadrzędnego stacking context. Wersja 1.5 jest większa od 1.3, ale nie ma szans na przebicie 2.4. Nie ważne jak duży z-index damy, mógłby on nawet wynosić 999999, a i tak nie przebije wersji 2.0:

Spróbujmy naprawić nasz poprzedni przykład. Zależy nam na tym, żeby kwadrat z klasą box-red pojawił się nad box-pink. Osiągamy to dzięki pozbyciu się warstwy, którą tworzył box-dashed za pomocą dwóch wcześniej wspomnianych właściwości - position: relative + z-index.

Chcemy nadal pozycjonować odpowiednio elementy, więc zostawiamy position: relative. Usuwamy za to z-index, który w tej kombinacji jest niezbędny do stworzenia warstwy.

/* Stacking Context: ❌ */
.wrapper {
  position: relative;
}

/* Stacking Context: ❌ */
.box-dashed {
  position: relative;
  /* z-index: 1; */
}

/* Stacking Context: ✅ */
.box-red {
  position: absolute;
  z-index: 3;
}

/* Stacking Context: ✅ */
.box-pink {
  position: absolute;
  z-index: 2;
}

Usuwając stacking context box-dashed zostajemy z trzema warstwami:

  • Dokument HTML
    • box-red
    • box-pink

Zagnieżdżone w domyślym stacking context dokumentu HTML, warstwy box-red oraz box-pink pozostają na tym samym poziomie i mogą być porównane ze sobą.

Co tworzy Stacking Context?

Warstwy możemy tworzyć na wiele różnych sposóbów. Powiedzieliśmy sobie, że połącznenie position: relative / position: abosolute + z-index tworzy nowy stacking context, czy jest coś jeszcze?

Z popularniejszych metod możemy wymienić:

  • Dokument HTML
  • Element z pozycją relative / abosolute + z-index (inne niż auto)
  • Element z pozycją sticky / fixed
  • Dziecko elementu, który ma display ustawiony na flex / grid
  • Element z opacity mniejszym niż 1

Całą listę znajdziesz w dokumentacji MDN.

Kolejność ułożenia w Stacking Context

Udało nam się stworzyć nową warstwę, ale nadal coś nie działa? 🤬

Elementy znajdujące się w obrębie danej warstwy układane będą względem danej kolejności:

  1. Wypozycjonowane elementy z negatywnym z-index
  2. Elementy z position: static
  3. Wypozycjonowane elementy z z-index: auto
  4. Wypozycjonowane elementy z pozytywnym lub zerowym z-index

Praktyczny przykład

Wróćmy do naszego początkowego problemu. W naszym sklepie nawigacja ma się znajdować pod, a koszyk nad resztą elementów.

Tworzymy odpowiednią strukturę HTML i nadajemy elementom odpowiednie z-index. Elementowi z klasą .main przypisujemy większy z-index, niż elementowi z klasą .header. Header jest rodzicem koszyka, więc dodatkowo nadajemy mu position: relative - dzięki temu będziemy mogli swobodnie umiejscowić koszyk za pomocą position: absolute.

<style>
  .header {
    position: relative;
    z-index: 1;
  }

  .cart {
    position: abosolute;
    top: 5rem;
    right: 5rem;
    z-index: 999999;
  }

  .main {
    position: relative;
    z-index: 10;
    transform: translateY(-50px);
  }
</style>

<body>
  <header class="header">
    <nav></nav>
    <div class="cart"></div>
  </header>
  <main class="main">
    <aside></aside>
    <ul></ul>
  </main>
</body>

Widzisz problem? 🤔

Element z klasą .header stworzył nowy stacking context. Aby poprawić wygląd naszego sklepu, potrzebujemy pozbyć się nowo utworzonej warsty. Kasujemy zbędny z-index: 1 z header i wyszystko zaczęło śmigać!

Od teraz warstwy koszyka i elementu z klasą <main> są na tym samym poziomie - dlatego możemy je bez obaw z sobą porównywać.

.header {
  position: relative;
}

.cart {
  position: absolute;
  top: 5rem;
  right: 5rem;
  z-index: 2;
}

.main {
  position: relative;
  z-index: 1;
  transform: translateY(-50px);
}

Uprościłem również wartości z-index - dbaj o swoje z-indexy i nie nadawaj im nieskończonych wręcz wartości. Zobacz jak utrzymywać w porządku swoje z-index - Managing CSS Z-Index In Large Projects

Warstwy w izolacji

Wyobraź sobie sytuację, w której tworzysz jakiś reużywaly kawałek strony. W naszym przypadku niech to będzie kafelek przedmiotu. Występuje on na stronie głównej, ale wyświetla się również na stronie z daną kategorią. Od dziś każda taka karta będzie miała w tle grafikę SVG.

Potrzebujemy wypozycjonować SVG tak, żeby znajdowało się pod resztą elementów. Jazda, dodajemy negatywny z-index i gotowe! Nie do końca, nasza grafika zniknęła z pola naszego widzenia - znajduje się pod samą kartą.

Jeśli pomyślałeś o stacking context, to masz w 100% racje, musimy go stworzyć!

Mamy naprawdę mnóstwo metod, dzięki którym stworzymy nową warstę. Problem jest tylko taki, że karta ma być reużywalna. Nie chcemy jej wprost podać z-index, bo nie wiemy jak będzie się zachowywała na innych stronach.

Całe na biało w takich przypadkach wjeżdża isolation: isolate w CSS. Pozwala nam ono stworzyć nowy stacking context bez żadnych skutków ubocznych. Świetna alternatywa dla innych sposóbów, które w przeciwieństwie do isolation, posiadają nieporządane efekty :)

Podsumowanie

Ustawianie elementów za pomocą z-index potrafi być naprawdę upierdliwe... Zrozumienie konceptu stacking context to podstawa do uniknięca drogich wizyt u psychologa.

Do usłyszenia!

Źródła

O autorze

Olaf Sulich

Olaf jest Frontend Developerem, blogerem i nosi rybacki kapelusz 🎩 Pisze o wszystkim co związane z frontendem, ale nie boi się backendu i designów 🦾 Ma głowę pełną pomysłów i nadzieję, że znajdziesz tutaj coś dla siebie!

Dołącz do społeczności!

Bo w programowaniu liczą się ludzie

Wchodzę