То, что вам никто не говорил о z-index

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

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

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

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

<div>
  <span class="red">Red</span>
</div>
<div>
  <span class="green">Green</span>
</div>
<div>
  <span class="blue">Blue</span>
</div>

 

.red, .green, .blue {
  position: absolute;
}
.red {
  background: red;
  z-index: 1;
}
.green {
  background: green;
}
.blue {
  background: blue;
}

Пример на jsfiddle

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

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

Решение:

Решение состоит в том, чтобы добавить прозрачность чуть меньше единицы первому <div> (родителю красного <span>).
Вот css, иллюстрирующий это:

div:first-child {
  opacity: .99;
}

Пример на jsfiddle

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

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

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 равным миллиону.

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

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

Первые два пункта кажутся логичными и, как правило, известны большинству веб-разработчиков.
Третий пункт (прозрачность) почти нигде не упоминается, кроме, собственно, спецификаций W3C.

Определение позиции элемента в наложении

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

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

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

  1. Корневой элемент контекста
  2. Позиционированный элемент (и его дети) с отрицательным z-index (элементы с большими значения располагаются перед элементами с меньшими значениями, при этом элементы с одинаковыми значениями располагаются в порядке их нахождения в html)
  3. Не позиционированные элементы (располагаются в порядке их нахождения в html)
  4. Позиционированные элементы (и их потомки) с значением z-index равным auto (располагаются в порядке их нахождения в html)
  5. Позиционированные элементы (и их потомки) с положительными значениями z-index (элементы с большими значения располагаются перед элементами с меньшими значениями, при этом элементы с одинаковыми значениями располагаются в порядке их нахождения в html)

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

Глобальный Порядок наложения

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

Заключение:

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

<div><!-- 1 -->
  <span class="red"><!-- 6 --></span>
</div>
<div><!-- 2 -->
  <span class="green"><!-- 4 --><span>
</div>
<div><!-- 3 -->
  <span class="blue"><!-- 5 --></span>
</div>

При добавлении прозрачности к первому <div> отображение меняется следующим образом:

<div><!-- 1 -->
  <span class="red"><!-- 1.1 --></span>
</div>
<div><!-- 2 -->
  <span class="green"><!-- 4 --><span>
</div>
<div><!-- 3 -->
  <span class="blue"><!-- 5 --></span>
</div>

Теперь красный <span>, который был обозначен как 6, стал 1.1. Я использую дробную нотацию, чтобы показать, что был сформирован новый контекст, и красный <span> является в нем первым элементом.

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

Оригинальный пример содержал 2 контекста, сформированные на корневом элементе и на красном <span>. После установки прозрачности родительскому элементу красного <span> мы сформировали третий контекст, и его z-index стал действовать только в пределах этого нового контекста. Именно поэтому первый <div> (тот самый, к которому мы применили прозрачность) и его братья (не позиционированные и без установленного z-index) стали располагаться в порядке следования в HTML структуре. А это значит, что первый <div> и все элементы, входящие в его контекст, оказались позади всех остальных.

Исходный пример: jsfiddle
Конечный результат: jsfiddle

UPD:
Данный способ не работает в Internet Eplorer 9/10 при позиционировании контейнера, как absolute/fixed.