Освоение z-index в CSS

Источник: «Mastering z-index in CSS»
z-index — свойство, используемое для управления порядком расположения слоёв в документе. Элементы с большим значением z-index располагаются над элементами с меньшим значением. Подобно тому, как оси x и y на странице определяют расположение элементов по горизонтали и вертикали, z-index определяет, как они располагаются друг над другом по оси z.

Наложение по умолчанию

При написании HTML элементы, расположенные ниже в документе, естественно, располагаются над элементами, расположенными выше.

<body>
<header class="site-header"></header>
<main class="site-content"></main>
<footer class="site-footer"></footer>
</body>

В этом фрагменте HTML footer будет располагаться поверх области содержимого main, которая будет располагаться поверх header, если все они будут расположены так, чтобы перекрывать друг друга.

Перекрытие элементов может осуществляться с помощью комбинации свойств position и смещения top, right, bottom и left.

Если для каждого из этих элементов установить position: absolute, то все они будут располагаться друг над другом. footer занимает последнее место в документе, поэтому по умолчанию он располагается поверх двух предыдущих элементов.

Если я использую свойства смещения, top и left, то мы можем увидеть порядок более наглядно.

.site-header, .site-content, .site-footer {
position: absolute;
width: 400px;
padding: 20px;
}
.site-header {top: 0; left: 0;}
.site-content {top: 50px; left: 50px;}
.site-footer {top: 100px; left: 100px;}

Контекст наложения

Хотя использование position: absolute и создаёт элементы, перекрывающие друг друга, мы ещё не создали так называемый контекст наложения (stacking context).

Контекст наложения создаётся любым из следующих способов:

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

Возвращаясь к предыдущему примеру, отметим, что у нас есть три элемента, расположенные друг над другом, но в данный момент y них нет значения css свойства z-index.

CSS свойство z-index позволяет управлять порядком наложения.

Если я установлю z-index: 1 для footer, z-index: 2 для main и z-index: 3 для header, то порядок наложения по умолчанию может быть полностью изменён на противоположный.

На первый взгляд это выглядит довольно просто: чем выше z-index, тем выше располагается элемент — так, z-index: 9999 всегда будет находиться поверх z-index: 9. К сожалению, все несколько сложнее.

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

<header class="site-header blue">header</header>
<main class="site-content green">content
<div class="box yellow"></div>
</main>
<footer class="site-footer pink">footer</footer>

Если я добавлю .box внутри контейнера site-content и расположу его чуть дальше правого нижнего угла, то мы увидим, что он находится над зелёным боксом и под розовым боксом.

.box {
position: absolute;
bottom: -25px;
right: -25px;
z-index: 4; /* won't work :( */
width: 75px;
height: 75px;
border: 1px solid #000;
}
.site-header {top: 0; left: 0; z-index: -1;}
.site-content {top: 50px; left: 50px;}
.site-footer {top: 100px; left: 100px; z-index: 3;}

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

Если я установлю z-index: 4, что выше, чем z-index: 3, мы не увидим никаких изменений. Обычно люди пытаются принудительно сложить значения, задавая огромное число, например 9999, но это также не даёт никакого эффекта. Подобные значения z-index, встречающиеся в кодовой базе, являются своеобразным запахом кода, поэтому старайтесь избегать их, если это возможно.

Причина, по которой мы не можем получить желаемый результат — жёлтый блок поверх розового, заключается в том, как ведёт себя z-индекс в контексте наложения.

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

<header class="site-header blue">
<h1>Header</h1>
<code>position: relative;<br/>
z-index: 5;</code>
</header>

<main class="site-content pink">
<div class="box1 yellow">
<h1>Content box 1</h1>
<code>position: relative;<br/>
z-index: 6;</code>
</div>

<h1>Main content</h1>
<code>position: absolute;<br/>
z-index: 4;</code>

<div class="box2 yellow">
<h1>Content box 2</h1>
<code>position: relative;<br/>
z-index: 1;</code>
</div>

<div class="box3 yellow">
<h1>Content box 3</h1>
<code>position: absolute;<br/>
z-index: 3;</code>
</div>
</main>

<footer class="site-footer green">
<h1>Footer</h1>
<code>position: relative;<br/>
z-index: 2;</code>
</footer>
.blue {background: hsla(190,81%,67%,0.8); color: #1c1c1c;}
.purple {background: hsla(261,100%,75%,0.8);}
.green {background: hsla(84,76%,53%,0.8); color: #1c1c1c;}
.yellow {background: hsla(61,59%,66%,0.8); color: #1c1c1c;}
.pink {background: hsla(329,58%,52%,0.8);}

header, footer, main, div {
position: relative;
border: 1px dashed #000;
}
h1 {
font: inherit;
font-weight: bold;
}
.site-header, .site-footer {
padding: 10px;
}
.site-header {
z-index: 5;
top: -30px;
margin-bottom: 210px;
}
.site-footer {
z-index: 2;
}
.site-content {
z-index: 4;
opacity: 1;
position: absolute;
top: 40px;
left: 180px;
width: 330px;
padding: 40px 20px 20px;
}
.box1 {
z-index: 6;
margin-bottom: 15px;
padding: 25px 10px 5px;
}
.box2 {
z-index: 1;
width: 400px;
margin-top: 15px;
padding: 5px 10px;
}
.box3 {
z-index: 3;
position: absolute;
top: 20px;
left: 180px;
width: 150px;
height: 250px;
padding-top: 125px;
text-align: center;
}

Здесь у нас, как и прежде, есть header, footer и main, но внутри site-content у нас есть три блока, которые были позиционированы и им был присвоен z-index.

Рассмотрим сначала три основных контейнера — header, footer и main.

header имеет z-index 5, поэтому отображается расположенным над main контентом, который имеет z-index: 4. footer имеет z-index 2, поэтому отображается под main с более высоким z-index 4. Все хорошо? Хорошо.

С тремя блоками внутри контейнера main все становится несколько запутанным.

box1 имеет z-index 6, но оказывается под header с меньшим z-index 5.

box2 имеет z-index 1, но появляется над footer, который имеет более высокий z-index 2.

Итак, что происходит?

Все это объясняется тем, что все значения z-index определяются в контексте их родительского наложения. Поскольку родительский контейнер .site-content имеет более высокий z-index, чем footer, любые позиционированные элементы внутри .site-content оцениваются в этом контексте.

Хорошим способом представления порядка наложения в контексте наложения является представление о нем как о подпункте во вложенном упорядоченном списке.

Поэтому, несмотря на то, что header имеет z-index: 5, а содержимое box1 имеет z-index: 6, порядок рендеринга равен 4,6, что все равно меньше, чем 5. Таким образом, содержимое box1 отображается ниже header.

Поначалу это немного запутанно, но с практикой все становится понятно!

z-index работает только для позиционированных элементов

Если необходимо управлять порядком расположения элементов, это можно сделать с помощью z-index. Однако z-index будет действовать только в том случае, если элемент также имеет position со значением absolute, relative или fixed.

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

В этом случае можно просто задать position: relative, но не указывать значения top, right, bottom или left. Элемент останется на прежнем месте на странице, поток документа не будет прерван, а значения z-index вступят в силу.

Вы можете использовать отрицательный z-index

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

Одна из областей, где это полезно, — использование псевдоэлементов и желание расположить их за содержимым родительского элемента.

В связи с тем, что контекст наложения работает, для элементов :before или :after требуется отрицательное значение z-index, если они должны располагаться за текстовым содержимым своего родительского элемента.

Посмотрите на следующий Codepen и поэкспериментируйте с различными значениями z-index.

See the Pen

Стратегия z-index

В заключение приведём простую стратегию применения z-index по всему проекту.

Ранее мы использовали одноразрядные инкременты для значений z-index, но что делать, если необходимо добавить новый элемент между двумя, имеющими значения z-index: 3 и z-index: 4? Пришлось бы менять множество значений — возможно, во всей кодовой базе, что могло бы стать проблематичным и привести к поломке CSS в других частях сайта.

Для задания z-index используйте шаг 100

При работе с z-index нередко можно встретить код, подобный этому:

.modal {
z-index: 99999;
}

На мой взгляд, это выглядит просто халтурно (и становится ещё хуже, если добавить к этому значению !important). Подобные значения часто являются симптомом того, что разработчик не понимает контекста наложения слоёв и пытается заставить один слой быть поверх другого.

Вместо того чтобы использовать произвольные числа типа 9999, 53 или 12, мы можем систематизировать шкалу z-index и привнести в процесс немного больше порядка. Это не потому, что у меня OCD разработчика. Честно.

Вместо того чтобы использовать для z-index увеличение на единицу, я использую увеличение на 100.

.layer-one {z-index: 100;}
.layer-two {z-index: 200;}
.layer-three {z-index: 300;}

Я делаю это для того, чтобы сохранить порядок, а также для того, чтобы не забывать о множестве различных слоёв, используемых в проекте. Ещё одно преимущество заключается в том, что если необходимо добавить новый слой между двумя другими, то между ними можно выбрать 99 потенциальных значений.

При построении системы z-index этот ручной подход является достаточно надёжным, но его можно сделать более гибким, если использовать возможности такого препроцессора, как Sass/SCSS.

Дополнительные материалы

Предыдущая Статья

Рефакторинг CSS: Стратегия, регрессионное тестирование и сопровождение (часть 2)

Следующая Статья

CSRF: Обход ограничений SameSite cookie-файлов