Очередной блог фрилансера. Свойство z-index: детальный обзор

От автора: сегодня будет разговор о том, как работает свойство z index CSS. Это третий пост в «Как работает CSS» — серия статей, где мы глубоко погружаемся в фундаментальные строительные блоки CSS, которые иногда похожи на черную магию. Независимо от того, как вы пишите CSS, всегда полезно знать, что «runtime» вашей таблицы стилей позволяет писать эффективный масштабируемый CSS.

Сегодня мы собираемся погрузиться в одну из тех частей черной магии CSS. Играя с z-index, мы часто бросаем все большие значения и смотрим, какие из них сработают. Но если мы снимем слои с z-index, и взглянем на правила, которые его определяют? Мы увидим, что это не так страшно, как мы думали.

Что такое Z-Index?

В конце концов, экраны, которые мы используем в браузере, представляют собой двумерные плоские поверхности с кучей пикселей. Но если вы в Интернете уже давно, скорее всего, вы часто имели «трехмерный» опыт. Например, вы можете щелкнуть по кнопке и открыть модальное окно или отобразить тултип «поверх» его кнопок.

Если вы предполагаете, что пользователь смотрит «на экран» и воображает, что экран представляет собой портал в трехмерный мир, вы можете начать визуализировать то, как выглядит это трехмерное пространство в браузере. Для начала, если бы мы визуализировали это пространство как комнату, то «стена», которая «находится дальше всего», называется «холст».

При рисовании пикселей на экране браузер гарантирует, что пиксели, которые ближе всего к холсту (или дальше от нас), будут нарисованы первее. Затем он постепенно продолжает рисовать элементы «ближе» к пользователю, перезаписывая ранее окрашенные пиксели.

Свойство z-index относится к порядку окраски элемента в этой иллюзии трехмерного браузера. По умолчанию все элементы имеют z-index 0, а браузер — в порядке DOM. Тем не менее, z-index фактически дает нам мелкозернистый контроль, когда элемент окрашивается. Назначая более высокий z-index мы можем заставить элемент окраситься таким образом, чтобы он был «ближе» к пользователю. Низкий (или отрицательный!) z-index позволяет нам рисовать элемент ближе к холсту.

Изучая CSS Position & Layout Spec, я нашел эту полезную диаграмму, чтобы визуализировать, как слои z-index смотрят на пользователя.

Элементы с более высоким z-index кажутся «ближе» к пользователю

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

Первая оговорка, связанная с z-index заключается в том, что свойство должно быть на позиционированном элементе, чтобы вступить в силу. Это означает, что z-index может использоваться только для изменения порядка укладки, если вы установили position отличную от static. В любой ситуации, когда вы не задали position для элемента, z-index будет иметь нулевой эффект.

Представление контекста стека

Краски вокруг z-index сгущаются еще сильнее, когда мы рассматриваем второе предостережение z-index: свойство применимо только к элементам, расположенным в контексте стека.

Контекст стека включает узел HTML и все его дочерние элементы. Элемент HTML на корневом уровне контекста стека можно назвать корнем стека.

Контекст стека по умолчанию (или «контекст корневого стека») для документа имеет тег html как его «корень стека», и все элементы относятся к этому стековому контексту по умолчанию. Однако любой узел HTML также может быть корневым элементом «локального контекста стека».

Вот несколько способов, которыми можно назначить элемент как корень нового локального контекста стека:

position: absolute или position: relative вместе с любым z-index отличным от auto для элемента.

Использование position: fixed или position: sticky на элементе.

Установка opacity меньше 1 на элементе.

Использование transform или will-change в элементе.

В качестве примера давайте представим три дерева узлов HTML, визуализированных как три стека. На приведенной ниже диаграмме нижняя часть каждого стека является родительским узлом HTML, а дочерние элементы располагаются сверху (например, представление «вверх дном» дерева HTML). Давайте также притворимся, что каждый из этих элементов является прямым дочерним элементом тега body.

Три стека элементов, родительские элементы находятся внизу

Если бы мы просмотрели эти элементы HTML в браузере, это выглядело бы примерно так:

Заметили что-нибудь странное со слоями?

Заметили что-нибудь интересное?

Во-первых, почему blue-child-2 ниже всего остального? Он имеет z-index в миллион, поэтому он должен быть сверху, правда?

Кроме того, почему green-child-1 появляется поверх всего, хотя у него z-index 2? Разве мы не говорили, что более высокие z-индексы «выигрывают» над более низкими z-индексами?

Начнем с green-child-1. Поскольку его родительский элемент — green-parent — имеет z-index 999 , мы ожидаем, что green-parent также будет выше всего остального. Но если вспомнить наше первое предупреждение об использовании z-index, это не повлияет на статически позиционированные элементы. Это означает, что green-child-1 является частью контекста корневого стека, и он измеряется против red-parent и blue-parent, единственных других двух элементов в корневом слое стека. Он имеет более высокий z-index чем тот, и он отображается сверху.

Понимание того, что red-parent и blue-parent каждый создают свои собственные локальные контексты стека, также помогает понять, почему blue-child-2 отображается ниже red-parent, хотя его z-index намного выше. Поскольку z-index управляет только позицией элемента в локальном контексте стека, blue-child-2 определенно будет выше всех детей blue-parent. Но у red-parent более высокий z-index, чем у blue-parent, все элементы внутри red-parent будут отображаться выше, чем blue-parent, независимо от их z-index.

Чтобы отобразить blue-child-2 над red-parent нам действительно пришлось бы изменить нашу структуру HTML, чтобы получить blue-child-2 из текущего локального контекста стека и в контекст корневого стека (или, по крайней мере, в стеке контекста, который выше red-parent).

Такие вещи часто могут быть сложными (даже вероломными) в более крупном приложении, особенно когда вы пытаетесь управлять такими вещами, как семантическая структура HTML, доступность и модульные архитектуры компонентов.

Вы часто будете встречать множество библиотек компонентов, которые реализуют свой многоуровневый пользовательский интерфейс (такие как тултипы и модальные окна), добавляя элементы в тег body а не туда, где вы включили компонент — это в значительной степени сделано для обхода проблемы с контекстом стека. Это гарантирует, что z-index на компонентах, которые в значительной степени полагаются на слои, всегда ссылается на контекст корневого стека, и поэтому можно задать z-index примерно 1000 и полагаться на то, что элемент будет отображаться поверх большинства элементов.

Заключение

Использование z-index может быть сложным в нескольких сценариях, но когда мы знаем правила того, как он работает внутри, мы обнаруживаем, что он действительно ведет себя вполне предсказуемо. Я бы предположил, что 99% путаницы вокруг z-index проистекает из непонимания двух подводных камней, которые мы обсуждали.

Быстро вспомним эти 2 предостережения.

z-index работает только с элементами, которые имеют значение position кроме static.

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

Надеюсь, что понимание внутреннего устройства z-index дает вам уверенность при создании многоуровневых UI с их различным ловушкам. Изучение z-index и слоев стека было очень полезно для меня для систематизации своего подхода к разработке UI – знать, какое число назначать намного лучше, чем просто бросать значения z-index.

Проблема z-index в том, что многие просто не понимают, как он работает.
Всё, описанное ниже, есть в спецификации W3C. К сожалению, не все её читают.

Описание проблемы:

Итак, пусть у нас есть HTML код, состоящий из 3 элементов.
Каждый из них внутри себя содержит по одному. А каждый, в свою очередь, имеет свой фон: красный, зеленый и синий, соответственно. Плюс ко всему, каждый позиционирован абсолютно левого верхнего края документа таким образом, что он немного перекрывает собой следующий за ним. Первый имеет z-index , равный 1, у остальных двух z-index не задан.

Ниже представлен HTML код с базовым css оформлением.

Red
Green
Blue

.red, .green, .blue { position: absolute; } .red { background: red; z-index: 1; } .green { background: green; } .blue { background: blue; }
Пример на jsfiddle

Задача: сделать так, чтобы красный был позади синего и зеленого, при этом не нарушая ни одно из следующих правил:

  • Нельзя менять HTML разметку
  • Нельзя менять/добавлять z-index к элементам
  • Нельзя менять/добавлять позиционирование к элементам

Решение:

Решение состоит в том, чтобы добавить прозрачность чуть меньше единицы первому (родителю красного).
Вот css, иллюстрирующий это:
div:first-child { opacity: .99; }

Хм, что-то тут не так. Причем тут вообще прозрачность? Каким образом она может влиять на порядок перекрытия элементов? Вы думаете так же? Добро пожаловать в клуб!
Надеюсь, во второй части статьи всё встанет на свои места.

Порядок наложения элементов:

Z-index кажется очень простым: чем значение больше, тем ближе к нам будет элемент, т.е. элемент с z-index 5 будет перекрывать собой элемент с z-index 2, верно? На самом деле нет.
Это и есть проблема z-index. Всё кажется настолько очевидным, что большинство разработчиков не уделяют достаточно времени для изучения этого вопроса.

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

Если свойства z-index и позиционирование не заданы явно, всё просто: порядок наложения равен порядку следования элементов в HTML. (На самом деле всё немного сложнее , но пока вы не будете использовать отрицательные значения отступов для перекрытия строчных элементов, вы не будете сталкиваться с крайними случаями)

Если вы явно указываете позиционирование элементам (и их детям), то такие элементы будут перекрывать собой элементы без явно заданного свойства позиционирования. (Говоря «явно указываете позиционирование» – я имею ввиду любое значение, кроме статического, например: абсолютное, или относительное).

И наконец, случай, когда z-index задан. Для начала, вполне естественно предполагать, что элементы с большим z-index будут находиться выше элементов с меньшим z-index, а любой элемент с установленным z-index будет находится выше элемента без установленного z-index, но это не так. Во первых, z-index учитывается только на явно позиционированных элементах. Если вы попробуете установить z-index на не позиционированный элемент, то ничего не произойдет. Во вторых, значения z-index могут создавать контексты наложения. Хм, всё стало намного сложнее, не так ли?

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

Элементы с общими родителями, перемещающиеся на передний или задний план вместе известны как контекст наложения. Понимание контекста наложения является ключом к пониманию z-index и порядка наложения элементов.

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

Новый контекст может быть сформирован в следующих случаях:

  • Если элемент – корневой элемент документа (элемент)
    Если элемент позиционирован не статически и его значение z-index не равно auto
    Если элемент имеет прозрачность менее 1

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

В нормальном потоке position: static элементы располагаются последовательно один за другим в том порядке, в котором они определены в html-документе. По умолчанию установлен z-index: auto; .


Рис. 1. Позиционирование элементов вдоль оси Z

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


Рис. 2. Воздействие свойства z-index на позиционированные элементы

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

Если для элементов свойства z-index и position не заданы явно, контекст наложения равен порядку их расположения в исходном коде. Если для элементов не задан z-index , браузер отображает элементы на странице в следующем порядке:

Корневой элемент

Позиционированные position элементы в порядке их следования в исходном коде. Последний из них будет расположен на переднем плане.




Рис. 3. Порядок расположения элементов в 3D пространстве по умолчанию

Lorem ipsum dolor sit amet, consectetur adipiscing elit...

Как видно из рисунка, элемент с position: absolute; находится на переднем плане, далее идет текст, под ним располагаются плавающие элементы с float: left; , не позиционированные блочные элементы расположены на заднем плане (так как плавающие и позиционированные элементы удаляются из потока, то блочные не позиционированные элементы их игнорируют и располагаются друг за другом, в соответствии с разметкой, поэтому элемент