Свойство 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
у его дочерних элементов, будет определять порядок их наложения только в пределах родительского элемента
У элемента формируется контекст наложения (то есть коробка закрывается), если выполняется одно из следующих условий:
- Элемент является корневым (
<html>
) - Элемент имеет свойство
position
со значениемrelative
илиabsolute
и свойствомz-index
со значением, отличным отauto
- то есть, с целочисленным значением - Элемент имеет свойство
position
со значениемsticky
илиfixed
- Элемент является дочерним элементом для родительского flexbox элемента и свойством
z-index
со значением, отличным отauto
- Элемент является дочерним элементом для родительского grid элемента и свойством
z-index
со значением, отличным отauto
- Элемент имеет свойство
opacity
меньше единицы - Элемент имеет свойство
mix-blend-mode
со значением, отличным отnormal
- Элемент имеет одно из следующих свойств со значением, отличным от
none
:transform
,filter
,perspective
,clip-path
,mask
/mask-image
/mask-border
- Элемент имеет свойство
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;
), и у них не сформированы контексты наложения - каждый следующий элемент накладывается на предыдущий, то есть отображается ближе к пользователю
На изображении видно, что жёлтый элемент находится позади всех элементов, чуть ближе - красный, еще чуть ближе - зелёный, еще ближе - синий, и самый ближайший элемент - тёмный, который перекрывает все предыдущие элементы
Как только любой из этих элементов становится позиционированным или формирует контекст наложения, то накладывается на все остальные не позиционированные элементы и элементы без сформированного контекста наложения, и становится ближайшим элементом к пользователю
Для удобства будем прописывать инлайн-стили и сразу смотреть результат
Сначала разместим жёлтый элемент поверх тёмного. Для этого сделаем жёлтый элемент позиционированным, то есть добавим свойство 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 (вместе со свойством 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>
Тёмный элемент снова перекрывает жёлтый элемент вместе с дочерними, так как действует тоже правило, что и при всех не позиционированных элементах - каждый следующий позиционированный элемент накладывается на предыдущий позиционированный элемент
Теперь, чтобы жёлтый элемент разместить ближе тёмного, необходимо жёлтому элементу добавить свойство 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>
Жёлтый элемент, вместе с дочерними, перекрывает тёмный элемент, так как теперь с помощью свойства 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>
Но получаем следующее
Почему тёмный элемент всё ещё перекрывает красный, ведь у красного элемента значение свойства 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>
Жёлтый родительский элемент остался позиционированным, но больше не имеет сформированного контекста наложения, так как не выполняется условие 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>
Выкладываю данный пример на 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>
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>
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?” для вас больше не актуален ;)