Порядок отрисовки CSS

Порядок отрисовки CSS

От автора: как браузер определяет, в каком порядке произвести отрисовку контента? Первое предположение может состоять в том, что браузеры будут рисовать контент в порядке, указанном в DOM, который для HTML-страницы соответствует порядку, который соответствует исходному коду страницы.

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

<style> .box { width: 8ex; height: 8ex; padding: 0.2ex; color: white; font-weight: bold; text-align: right; } .blue { background: #99DDFF; } /* Второй div имеет отрицательное верхнее поле, поэтому он перекрывается первым (синим) div. Также смещается немного вправо. */ .green { background: #44BB99; margin-left: 3ex; margin-top: -6ex; }
</style> <div class="blue box">1</div>
<div class="green box">2</div>

Порядок отрисовки CSS

Кажется, наше предположение было верным! Я уверен, что некоторые из вас говорят: «Эй! Как насчет z-index?» Вы правы. Используя свойство z-index, мы можем переопределить обычный порядок отрисовки, используемый браузером. Мы задаем зеленому div z-index и делаем его позиционированным относительно, потому что z-index работает только с позиционированными элементами. Мы также добавляем желтый дочерний элемент зеленого div, чтобы увидеть, как это влияет на потомков. Наконец, давайте начнем маркировать каждый div z-индексом.

<style> .yellow { margin-left: 3ex; background: #EEDD88; }
</style> <div class="blue box">0</div>
<div class="green box" style="position: relative; z-index: -1;">-1 <div class="yellow box">-1</div>
</div>

Порядок отрисовки CSS

В этом примере зеленый div отображается перед синим div, хотя в исходном коде он указан позже. Мы видим, что это z-index влияет на сам div и на желтый дочерний div. Что, если мы хотим вывести желтого вложенного потомка поверх всего, придав ему большой положительный z-index?

<div class="blue box">0</div>
<div class="green box" style="position: relative; z-index: -1;">-1 <div class="yellow box" style="position: relative; z-index: 1000;">1000</div>
</div>

Порядок отрисовки CSS

Подождите! Что тут происходит? У синего div не указано z-index, что должно означать, что значение, используемое для его, z-index равно нулю. z-index вложенного желтого потомка 1000, но этот div все еще отрисован под ним. Почему вложенный ребенок не отрисован поверх синего элемента, как мы могли бы ожидать?

На этом этапе уместно купить кружку с классической шуткой «CSS IS AWESOME», налить себе кофе и прочитать всю спецификацию CSS2. Внезапно мы понимаем, что ответ таков: наши дивы образуют нечто, называемое контекстом стекирования.

Контекст стекирования

Мы точно определили, что происходит, когда подошли к Приложению E: Подробное описание контекстов стекирования. К счастью, мы поспешили налить себе большую чашку кофе, так как вся полезная информация, очевидно, содержится в приложениях. Приложение E дает нам представление о алгоритме, который браузеры используют для определения порядка отрисовки контента на странице, включая то, какие свойства влияют на этот порядок. Оказывается, наши ранние догадки были в основном правильными, вещи обычно стекируются в соответствии с порядком в DOM и активными z-индексами. Однако иногда определенные свойства CSS, применяемые к элементам, инициируют создание контекста стекирования, который может повлиять на порядок отрисовки так, как мы этого не ожидаем.

Из Приложения E мы узнаем, что контекст стекирования — это атомарно отрисовки набора элементов страницы. Что это значит? Проще говоря, это означает, что вещи внутри контекста стекирования отрисованы вместе, как единое целое, и что элементы вне содержимого стека никогда не будут отрисованы между ними. Наличие активного z-index — это одна из основных ситуаций в CSS, которая запускает создание контекста стекирования. Есть ли способ, которым мы можем настроить наш пример выше, чтобы третий элемент принадлежал тому же контексту стекирования, что и первые два элемента? Ответ в том, что мы должны удалить его из контекста стекирования, созданного вторым элементом.

<div class="blue box">0</div>
<div class="yellow box" style="position: relative; z-index: 1000; margin-top: -5ex">1000</div>
<div class="green box" style="position: relative; z-index: -1; margin-left: 6ex;">-1</div>

Порядок отрисовки CSS

Теперь желтый div — это брат синего и зеленого цветов, и он отрисован поверх них обоих, хотя теперь он занимает второе место в источнике.

Понятно, что контексты стекирования могут накладывать сильные ограничения на порядок отрисовки элементов, поэтому было бы здорово знать, когда мы их запускаем. Независимо от того, запускает ли конкретная функция CSS создание нового контекста стекирования, эта функция определяется, что означает, что информация распределена по нескольким спецификациям. Полезно, что на MDN есть большой список ситуаций, которых элемент создает контекст стекирования. Некоторыми примечательными примерами являются элементы с активным z-index, position: fixed, а также элементы с position: sticky и элементы с transform или perspective.

Удивительные детали

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

Для меня одним из самых удивительных исключений создания контекста стекирования является overflow: scroll. Мы знаем, что установка для свойства overflow значения scroll приводит к тому, что все содержимое, которое выходит за край контейнера, будет скрыто в области с возможностью прокрутки. Что это означает, что они не вызывают создание контекста стекирования? Это означает, что содержимое вне прокручиваемой области может пересекать содержимое внутри нее. Все, что нужно, это немного поработать, чтобы увидеть это в действии:

<style> .scroll-area { overflow: scroll; border: 3px solid salmon; width: 18ex; height: 15ex; margin-left: 2ex; } .scroll-area .vertical-bar { position: relative; /* Мы задаем для каждого блока position: relative, поэтому они могут иметь z-indice. */ float: left; height: 50ex; width: 4ex; /* Полосатый фон показывает движение прокрутки. */ background: repeating-linear-gradient( 120deg, salmon 0px, salmon 10px, orange 10px, orange 20px ); } /* Четный блок будет сверху желтого вертикального блока из-за большего z-index. */ .scroll-area .vertical-bar:nth-child(even) { z-index: 4; opacity: 0.9; } .yellow-horizontal-bar { margin-top: -10ex; margin-bottom: 10ex; max-width: 30ex; background: #EEDD88; /* Поднимаем горизонтальный блок поверх полосы прокрутки области прокрутки. */ position: relative; z-index: 2; }
</style> <!-- Область прокрутки с четырьмя вертикальными блоками. -->
<div class="scroll-area"> <div class="vertical-bar"></div> <div class="vertical-bar"></div> <div class="vertical-bar"></div> <div class="vertical-bar"></div>
</div> <!-- div, который разместится между вертикальным блоком области прокрутки выше. -->
<div class="yellow-horizontal-bar">~~JUST PASSING THROUGH~~</div>

Порядок отрисовки CSS

Используя возможности веб-дизайна, нам удалось втиснуть последний div между содержимым области прокрутки. Половина прокручиваемого содержимого находится сверху постороннего предмета, а половина — снизу. Это, вероятно, удивительным образом отображает вставленный элемент div в верхней части полосы прокрутки области прокрутки (если она есть). Вы можете представить, какие головные боли это вызывает для реализации областей прокрутки в механизмах браузера, потому что дочерние элементы определенной области прокрутки могут быть распределены по всему дереву макета. Нет никакой гарантии, что он имеет какой-либо рекурсивной инкапсуляции.

Правила CSS часто имеют аргументированное происхождение, но некоторые из них являются просто произвольными решениями по реализации, принятыми примерно 20 лет назад без ретроспективного анализа. Такие грубые вещи, как это исключение из контекста стекирования, могут редко встречаться, но Сеть огромна и в ней много контента. Потенциально существуют тысячи страниц, полагающихся на это поведение, таких как списки самых ярых ангорских кроликов 2003 года или памятники чьей-то странной одержимости разметкой на обочине. Архитекторы сети решили не ломать эти галереи великолепных лагоморфов и вместо этого сделали выбор в пользу максимальной долгосрочной веб-совместимости.

Исключения из правил

Ранее я сказал, что ничто извне контекста стекирования не может быть отрисовано между контентом контекста стекирования. Это правда? CSS настолько велик, что должно быть хотя бы одно исключение, верно? Теперь у меня есть конкретный ответ на этот вопрос, и этот ответ «возможно». CSS полон мощных вещей, и одна из самых мощных вещей (это предвещает будущий пост) — это CSS-преобразования. Это имеет смысл. Контексты стекирования — связаны с наведением порядка в хаосе оси z, который простирается прямо от вашего сердца к экрану. Преобразованные элементы могут пересекать это измерение, обеспечивая яркие эффекты флипбука, а также требуя, чтобы веб-браузеры постепенно становились полноценными 3D-композиторами. Конечно, если возможно нарушить это правило, мы можем сделать это с помощью трехмерных CSS-преобразований.

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

<style> .salmon { background: salmon; margin-top: -5ex; margin-left: 4ex; }
</style> <div class="blue box">0</div>
<div style="position: relative; z-index: -2;"> <div class="green box">-2</div> <div class="salmon box">-2</div>
</div>

Порядок отрисовки CSS

Теперь мы сделаем две модификации этого примера. Сначала мы обернем пример в новый div с помощью transform-style со значением preserve-3d, что поместит всех потомков в трехмерное пространство. Наконец, мы отодвигаем один из div с z-index -2 от экрана, используя 3d-переход.

<div style="transform-style: preserve-3d;"> <div class="blue box">0</div> <div style="position: relative; z-index: -2;"> <div class="green box">-2</div> <div class="salmon box" style="transform: translateZ(50px);">-2</div> </div>
</div>

Порядок отрисовки CSS

Возможно, ваш браузер может не отображать это таким же образом, но в Chrome div с z-index 0 отображается между двумя div в одном и том же контексте стекирования и с z-index -2.

Мы нарушили основное правило контекста стекирования. Как вам такое, архитекторы сети! Полезно ли это упражнение вообще? Почти наверняка нет. Я надеюсь, что это было достаточно любопытно, по крайней мере!

Заключение

Надеюсь, я скоро вернусь, чтобы поговорить о реализации этой замечательной ерунды в Servo. Я хочу поблагодарить Фредерика Вана за вклад в этот пост, а также Mozilla за то, что они позволили мне выполнить это как часть моей работы для Igalia. Servo — отличный способ принять участие в разработке браузера. Он также написан на Rust, который является языком, который может помочь вам стать лучшим программистом, просто изучая его, так что проверьте его. Спасибо за прочтение!

Автор: Martin Robinson

Источник: https://abandonedwig.info

Редакция: Команда webformyself.