Свойство z-index и контекст наложения

Свойство z-index c первого взгляда кажется простым, и это не далеко от истины, но чтобы полноценно разобраться в его работе, необходимо узнать о такой концепции, как контекст наложения (stacking context)

Свойство z-index

Свойство z-index определяет порядок наложения элементов по оси Z - то есть ближе или дальше от взгляда пользователя.

Свойство z-index принимает целочисленные значения. Чем значение меньше - тем элемент дальше, чем значение больше - тем элемент ближе. Также, свойство z-index может принимать отрицательные значения. По-умолчанию имеет значение auto

Свойство z-index работает только с позиционированными элементами. По-умолчанию, у всех элементов, свойство position имеет значение static - такой элемент является не позиционированным. Чтобы свойство z-index работало, элементу необходимо задать свойство position с одним из следующих значений: relative, absolute, sticky, fixed

Для полного понимания, как работает свойство z-index, рассмотрим такую концепцию, как контекст наложения



Контекст наложения (stacking context)

Для простоты понимания, представим, что каждый элемент - это абстрактная коробка. Корневой элемент <html> - это основная, самая большая коробка, в которую будут складываться другие коробки поменьше. Коробки могут быть открытыми и закрытыми. Если коробка закрыта, то внутренние коробки не могут выйти за пределы этой коробки. Закрытая коробка - это и есть контекст наложения.

Другими словами, если у родительского элемента сформирован контекст наложения, то свойство z-index у его дочерних элементов, будет определять порядок их наложения только в пределах родительского элемента

У элемента формируется контекст наложения (то есть коробка закрывается), если выполняется одно из следующих условий:

  1. Элемент является корневым (<html>)
  2. Элемент имеет свойство position со значением relative или absolute и свойством z-index со значением, отличным от auto - то есть, с целочисленным значением
  3. Элемент имеет свойство position со значением sticky или fixed
  4. Элемент является дочерним элементом для родительского flexbox элемента и свойством z-index со значением, отличным от auto
  5. Элемент является дочерним элементом для родительского grid элемента и свойством z-index со значением, отличным от auto
  6. Элемент имеет свойство opacity меньше единицы
  7. Элемент имеет свойство mix-blend-mode со значением, отличным от normal
  8. Элемент имеет одно из следующих свойств со значением, отличным от none: transform, filter, perspective, clip-path, mask/mask-image/mask-border
  9. Элемент имеет свойство isolation со значением isolate

Есть еще несколько специфичных свойств, при которых также формируется контекст наложения, с ними можно познакомится на MDN Web Docs



Порядок наложения

Для наглядности сверстаем два элемента, один из которых будет содержать в себе еще три дочерних элемента.

За основу возьмём следующую HTML структуру

<div class="boxes">
  <div class="box box_yellow"> <!-- жёлтый элемент -->
    <div class="box__inner box__inner_red"></div> <!-- красный элемент -->
    <div class="box__inner box__inner_green"></div> <!-- зелёный элемент -->
    <div class="box__inner box__inner_blue"></div> <!-- синий элемент -->
  </div>
  <div class="box box_dark"></div> <!-- тёмный элемент -->
</div>

И пропишем для неё стили

.boxes {
  padding: 30px;
  display: inline-block;
}
.box {
  width: 344px;
  height: 344px;
  padding: 30px;
  border-radius: 15px;
  border: 2px dashed #111;
}
.box_yellow {
  background: rgba(255,209,102,0.8);
}
.box_dark {
  margin-left: 177px;
  margin-top: -177px;
  background: rgba(7,59,76,0.8);
}
.box__inner {
  width: 200px;
  height: 200px;
  border-radius: 15px;
}
.box__inner_red {
  border: 2px dashed #111;
  background: rgba(239,71,111,0.8);
}
.box__inner_green {
  margin-top: -160px;
  margin-left: 40px;
  border: 2px dashed #111;
  background: rgba(6,214,160,0.8);
}
.box__inner_blue {
  margin-top: -160px;
  margin-left: 80px;
  background: #118ab2;
  border: 2px dashed #111;
  background: rgba(17,138,178,0.8);
}

Пока все элементы не позиционированы (по-умолчанию, у всех элементов position: static;), и у них не сформированы контексты наложения - каждый следующий элемент накладывается на предыдущий, то есть отображается ближе к пользователю

1

На изображении видно, что жёлтый элемент находится позади всех элементов, чуть ближе - красный, еще чуть ближе - зелёный, еще ближе - синий, и самый ближайший элемент - тёмный, который перекрывает все предыдущие элементы

Как только любой из этих элементов становится позиционированным или формирует контекст наложения, то накладывается на все остальные не позиционированные элементы и элементы без сформированного контекста наложения, и становится ближайшим элементом к пользователю

Для удобства будем прописывать инлайн-стили и сразу смотреть результат


Сначала разместим жёлтый элемент поверх тёмного. Для этого сделаем жёлтый элемент позиционированным, то есть добавим свойство position:relative

<div class="boxes">
  <div class="box box_yellow" style="position: relative;"> <!-- жёлтый элемент -->
    <div class="box__inner box__inner_red"></div> <!-- красный элемент -->
    <div class="box__inner box__inner_green"></div> <!-- зелёный элемент -->
    <div class="box__inner box__inner_blue"></div> <!-- синий элемент -->
  </div>
  <div class="box box_dark"></div> <!-- тёмный элемент -->
</div>

2

Жёлтый элемент становится позиционированным и накладывается вместе с дочерними элементами на тёмный не позиционированный элемент.

Но жёлтый элемент пока еще не формирует контекста наложения, так как не выполнено условие 2 (вместе со свойством position: relative; должно быть свойство z-index с целочисленным значением)


Теперь добавим position: relative; и для тёмного элемента

<div class="boxes">
  <div class="box box_yellow" style="position: relative;"> <!-- жёлтый элемент -->
    <div class="box__inner box__inner_red"></div> <!-- красный элемент -->
    <div class="box__inner box__inner_green"></div> <!-- зелёный элемент -->
    <div class="box__inner box__inner_blue"></div> <!-- синий элемент -->
  </div>
  <div class="box box_dark" style="position: relative;"></div> <!-- тёмный элемент -->
</div>

1

Тёмный элемент снова перекрывает жёлтый элемент вместе с дочерними, так как действует тоже правило, что и при всех не позиционированных элементах - каждый следующий позиционированный элемент накладывается на предыдущий позиционированный элемент


Теперь, чтобы жёлтый элемент разместить ближе тёмного, необходимо жёлтому элементу добавить свойство z-index с большим значением, чем у тёмного

Назначим жёлтому элементу z-index: 2;, а тёмному - z-index: 1

<div class="boxes">
  <div class="box box_yellow" style="position: relative; z-index: 2;"> <!-- жёлтый элемент -->
    <div class="box__inner box__inner_red"></div> <!-- красный элемент -->
    <div class="box__inner box__inner_green"></div> <!-- зелёный элемент -->
    <div class="box__inner box__inner_blue"></div> <!-- синий элемент -->
  </div>
  <div class="box box_dark" style="position: relative; z-index: 1;"></div> <!-- тёмный элемент -->
</div>

2

Жёлтый элемент, вместе с дочерними, перекрывает тёмный элемент, так как теперь с помощью свойства z-index мы изменили порядок наложения. У которого элемента значение свойства z-index будет больше, тот и будет отображаться ближе к пользователю.

При этом у нас выполнилось условие 2 и жёлтый элемент сформировал контекст наложения. Но на что это влияет?


К примеру, была поставлена задача, чтобы жёлтый элемент был позади тёмного, а красный элемент всегда был ближайшим элементом к пользователю.

Назначаем жёлтому элементу z-index: 1;, а тёмному z-index: 2;

Назначаем красному элементу свойства position: relative; и z-index: 9999;

<div class="boxes">
  <div class="box box_yellow" style="position: relative; z-index: 1;"> <!-- жёлтый элемент -->
    <div class="box__inner box__inner_red" style="position: relative; z-index: 9999;"></div> <!-- красный элемент -->
    <div class="box__inner box__inner_green"></div> <!-- зелёный элемент -->
    <div class="box__inner box__inner_blue"></div> <!-- синий элемент -->
  </div>
  <div class="box box_dark" style="position: relative; z-index: 2;"></div> <!-- тёмный элемент -->
</div>

Но получаем следующее

3

Почему тёмный элемент всё ещё перекрывает красный, ведь у красного элемента значение свойства z-index значительно больше?

Вспоминаем про контекст наложения и закрытые коробки.

Так как у жёлтого родительского элемента сформирован контекст наложения (жёлтая коробка закрыта), то его дочерние элементы не могут выйти за пределы родительского значения z-index: 1. Все значения z-index для дочерних элементов теперь будут применяться только в пределах жёлтого родительского элемента, a для тёмного элемента они не будут превышать значения родительского z-index: 1


Так как же всё-таки разместить красный элемент ближе остальных?

В данном случае, нужно избавиться от контекста наложения жёлтого родительского элемента - есть два варианта

1 вариант - назначить ему position: static; - контекста наложения больше не будет и z-index перестанет работать

2 вариант - убрать свойство z-index: 1; (или назначить z-index: auto;), но оставить position: relative;, если жёлтый элемент должен оставаться позиционированным, что встречается гораздо чаще

Выбираем второй вариант

<div class="boxes">
  <div class="box box_yellow" style="position: relative; z-index: auto;"> <!-- жёлтый элемент -->
    <div class="box__inner box__inner_red" style="position: relative; z-index: 9999;"></div> <!-- красный элемент -->
    <div class="box__inner box__inner_green"></div> <!-- зелёный элемент -->
    <div class="box__inner box__inner_blue"></div> <!-- синий элемент -->
  </div>
  <div class="box box_dark" style="position: relative; z-index: 2;"></div> <!-- тёмный элемент -->
</div>

4

Жёлтый родительский элемент остался позиционированным, но больше не имеет сформированного контекста наложения, так как не выполняется условие 2, поэтому он больше не ограничивает дочерние элементы своим значением свойства z-index, в том числе и красный


Теперь красный элемент может быть даже позади жёлтого

Назначим красному элементу z-index: -1;

<div class="boxes">
  <div class="box box_yellow" style="position: relative; z-index: auto;"> <!-- жёлтый элемент -->
    <div class="box__inner box__inner_red" style="position: relative; z-index: -1;"></div> <!-- красный элемент -->
    <div class="box__inner box__inner_green"></div> <!-- зелёный элемент -->
    <div class="box__inner box__inner_blue"></div> <!-- синий элемент -->
  </div>
  <div class="box box_dark" style="position: relative; z-index: 2;"></div> <!-- тёмный элемент -->
</div>

5

Выкладываю данный пример на Codepen, где можно попрактиковаться со свойством z-index и контекстом наложения



Другие условия формирования контекста наложения

Выше были описаны условия формирования контекста наложения, рассмотрим их немного подробнее на предыдущем примере.

При формировании контекста наложения, красный элемент не сможет перекрыть тёмный, хоть и имеет большее значение z-index

1. Элемент является корневым (<html>)

У <html>, как у корневого элемента, всегда сформирован контекст наложения

2. Элемент имеет свойство position со значением relative или absolute и свойством z-index со значением, отличным от auto - то есть, с целочисленным значением

Подробно рассмотрели выше

3. Элемент имеет свойство position со значением sticky или fixed

Если элементу назначены position: sticky; или position: fixed;, то контекст наложения у этого элемента формируется без свойства z-index. Но для указания положения среди других элементов, оно будет использоваться, и его дочерние элементы не смогут выходить за пределы его значения z-index

<div class="boxes">
  <div class="box box_yellow" style="position: sticky;"> <!-- жёлтый элемент -->
    <div class="box__inner box__inner_red" style="position: relative; z-index: 9999;"></div> <!-- красный элемент -->
    <div class="box__inner box__inner_green"></div> <!-- зелёный элемент -->
    <div class="box__inner box__inner_blue"></div> <!-- синий элемент -->
  </div>
  <div class="box box_dark" style="position: relative; z-index: 2;"></div> <!-- тёмный элемент -->
</div>

4. Элемент является дочерним элементом для родительского flexbox элемента и свойством z-index со значением, отличным от auto

5. Элемент является дочерним элементом для родительского grid элемента и свойством z-index со значением, отличным от auto

Если есть элементы со свойствами display: flex; или display: grid;, а у его дочернего элемента назначено свойство z-index c любым целочисленным значением, то у этого дочернего элемента формируется контекст наложения. То есть если у этого дочернего элемента, есть свои дочерние элементы, то они не смогут выходить за его пределы z-index

6. Элемент имеет свойство opacity меньше единицы

Если у элемента значение свойства непрозрачности opacity меньше единицы, то также формируется контекст наложения

<div class="boxes">
  <div class="box box_yellow" style="opacity: 0.99"> <!-- жёлтый элемент -->
    <div class="box__inner box__inner_red" style="position: relative; z-index: 9999;"></div> <!-- красный элемент -->
    <div class="box__inner box__inner_green"></div> <!-- зелёный элемент -->
    <div class="box__inner box__inner_blue"></div> <!-- синий элемент -->
  </div>
  <div class="box box_dark" style="position: relative; z-index: 2;"></div> <!-- тёмный элемент -->
</div>

7. Элемент имеет свойство mix-blend-mode со значением, отличным от normal

Если у элемента значние свойства режима смешивания mix-blend-mode отличное от normal, то тоже формируется контекст наложения

<div class="boxes">
  <div class="box box_yellow" style="mix-blend-mode: luminosity;"> <!-- жёлтый элемент -->
    <div class="box__inner box__inner_red" style="position: relative; z-index: 9999;"></div> <!-- красный элемент -->
    <div class="box__inner box__inner_green"></div> <!-- зелёный элемент -->
    <div class="box__inner box__inner_blue"></div> <!-- синий элемент -->
  </div>
  <div class="box box_dark" style="position: relative; z-index: 2;"></div> <!-- тёмный элемент -->
</div>

6

8. Элемент имеет одно из следующих свойств со значением, отличным от none: transform, filter, perspective, clip-path, mask/mask-image/mask-border

Если у любого из этих свойств, будет назначено значение отличное от none, то формируется контекст наложения

<div class="boxes">
  <div class="box box_yellow" style="transform: scale(.5)"> <!-- жёлтый элемент -->
    <div class="box__inner box__inner_red" style="position: relative; z-index: 9999;"></div> <!-- красный элемент -->
    <div class="box__inner box__inner_green"></div> <!-- зелёный элемент -->
    <div class="box__inner box__inner_blue"></div> <!-- синий элемент -->
  </div>
  <div class="box box_dark" style="position: relative; z-index: 2;"></div> <!-- тёмный элемент -->
</div>

7

9. Элемент имеет свойство isolation со значением isolate

Также есть специальное свойство isolation, которое при необходимости может принудительно сформировать контекст наложения, если назначить ему значение isolate

<div class="boxes">
  <div class="box box_yellow" style="isolation: isolate;"> <!-- жёлтый элемент -->
    <div class="box__inner box__inner_red" style="position: relative; z-index: 9999;"></div> <!-- красный элемент -->
    <div class="box__inner box__inner_green"></div> <!-- зелёный элемент -->
    <div class="box__inner box__inner_blue"></div> <!-- синий элемент -->
  </div>
  <div class="box box_dark" style="position: relative; z-index: 2;"></div> <!-- тёмный элемент -->
</div>


Итоги

Статья получилась довольно объёмной. Старался объяснить эту тему максимально понятно со множеством примеров.

Буду рад, если статья оказалась полезной, и частый вопрос “почему не работает z-index?” для вас больше не актуален ;)



Полезные ссылки

The stacking context

Understanding CSS z-index