Positioning, stacking context и formatting contexts
Позиционирование — один из самых частых источников путаницы в CSS. Разработчики знают, что position: absolute выводит элемент из потока, но теряются, когда речь заходит о containing block, stacking context’ах и порядке отрисовки. Вторая половина картины — formatting contexts: области, в которых блоки раскладываются по единым правилам. Понимание этих механизмов объясняет большинство «магических» поведений вёрстки: почему margin «выпадает» из родителя, почему transform ломает fixed и почему z-index: 9999 не всегда перекрывает соседа.
Содержание
- Глубокий разбор
- Formatting contexts
- Практические примеры
- Типичные ошибки и антипаттерны
- Ключевые тезисы для интервью
- Заключение
- Полезные ссылки
Глубокий разбор
Типы позиционирования
CSS предлагает пять значений position:
static— значение по умолчанию. Элемент находится в нормальном потоке, свойства смещения (top,right,bottom,left,inset) игнорируются.relative— элемент остаётся в потоке, но может смещаться относительно своего нормального положения.absolute— элемент выводится из потока и позиционируется относительно containing block.fixed— элемент позиционируется относительно viewport (или containing block, если он создаётсяtransform/filter/perspective/containу предка).sticky— гибридrelativeиfixed: элемент ведёт себя какrelative, пока не достигнет заданного порога в пределах своего ближайшего прокручиваемого предка.
position: relative
Relative positioning (относительное позиционирование) смещает элемент, не меняя его место в потоке. Окружающие элементы по-прежнему считают, что он занимает исходную позицию.
.box {
position: relative;
top: 20px;
left: 20px;
}
Элемент сдвинется вниз и вправо на 20px, но пространство, которое он занимал, останется пустым. Это удобно для небольших сдвигов, но плохо подходит для раскладки.
position: absolute
Absolute positioning (абсолютное позиционирование) полностью выводит элемент из нормального потока. Его размер и местоположение определяются относительно containing block (содержащего блока).
Containing block для absolute — это ближайший предок, у которого position не static, либо предок с transform, filter, perspective или contain: paint/layout. Если такого нет — containing block станет <html>.
.card {
position: relative;
}
.badge {
position: absolute;
top: 8px;
right: 8px;
}
Важные нюансы:
- Абсолютно позиционированный элемент сжимается до ширины контента, если явно не задана ширина.
margin: autoв сочетании сinset: 0и фиксированными размерами центрирует элемент в containing block.
position: fixed
Fixed positioning (фиксированное позиционирование) похоже на absolute, но containing block по умолчанию — viewport. Элемент не прокручивается вместе со страницей.
.toast {
position: fixed;
bottom: 24px;
right: 24px;
}
Исключение: если предок имеет transform, filter, perspective или contain, он становится containing block’ом для fixed, и элемент начинает позиционироваться относительно него. Это часто ломает модальные окна, вложенные в анимированные контейнеры.
position: sticky
Sticky positioning (липкое позиционирование) требует указания хотя бы одного порога (top, right, bottom, left). Элемент ведёт себя как relative, пока его позиция в прокручиваемом контейнере не достигнет порога. После этого он «прилипает».
.header {
position: sticky;
top: 0;
}
Условия работы:
- Ближайший предок с прокруткой должен быть выше sticky-элемента в DOM.
- Родительский контейнер должен быть достаточно высоким, чтобы была область для «прилипания».
- Свойство
overflowу предков может влиять на поведение sticky.
Stacking context
Stacking context (контекст наложения) — это трёхмерная концепция: элементы внутри одного stacking context’а рисуются слоями от дальних к ближним. z-index работает только внутри одного stacking context’а и не позволяет элементу «пробить» границу родительского контекста.
Stacking context создаётся:
- Корневой элемент (
<html>). - Элемент с
position: absolute/relative/fixed/stickyи явнымz-index, отличным отauto. - Элемент с
opacityменьше 1. - Элемент с
transform,filter,perspective,clip-path,mask. - Элемент с
isolation: isolate. - Элемент с
mix-blend-mode, отличным отnormal. - Flex/grid-контейнер, у которого дети имеют
z-index, отличный отauto. - Элемент с
will-change. - Элемент с
contain: layout/paint/strict/content.
z-index
z-index управляет порядком наложения элементов внутри одного stacking context’а. Большое значение рисуется ближе к пользователю.
.modal {
position: fixed;
z-index: 100;
}
.tooltip {
position: absolute;
z-index: 10;
}
Ключевые моменты:
z-indexне работает дляposition: static.z-index: autoне создаёт нового stacking context’а.- Дочерний элемент с огромным
z-indexне может выйти за пределы stacking context’а родителя. Если родитель.tooltipлежит под.modal, дочерний элемент.tooltipне перекроет.modal, сколько быz-indexему ни задали.
Порядок отрисовки внутри stacking context
Внутри одного stacking context’а браузер рисует элементы в таком порядке (от дальнего к ближнему):
- Фон и border контекста.
- Дочерние элементы с отрицательным
z-index. - Элементы в нормальном потоке (
static,relativeбезz-index). - Плавающие элементы (
float). - Строчные элементы (inline).
- Дочерние элементы с
positionиz-index: auto(или безz-index). - Дочерние элементы с положительным
z-index.
Это объясняет, почему иногда position: relative без z-index перекрывает float, а иногда нет: порядок рисования зависит от комбинации факторов.
Formatting contexts
Что такое formatting context
Formatting context — это область документа, внутри которой блоки раскладываются по единому набору правил. Все элементы внутри одного контекста влияют друг на друга: например, вертикальные margin’ы блоков в нормальном потоке схлопываются, а inline-элементы распределяются по строкам.
Существует четыре основных типа:
- BFC — Block Formatting Context.
- IFC — Inline Formatting Context.
- FFC — Flex Formatting Context.
- GFC — Grid Formatting Context.
Block Formatting Context (BFC)
BFC (блочный контекст форматирования) — это область, в которой блочные элементы располагаются вертикально друг под другом, а margin’ы между ними схлопываются.
Элемент создаёт новый BFC, если у него:
floatнеnone;positionравноabsoluteилиfixed;display: inline-block,table-cell,table-caption,flow-root;overflowнеvisible;display: flexилиgridу самого элемента (для его детей создаётся FFC/GFC, но сам flex/grid-контейнер тоже изолирован).
Самый современный и чистый способ создать BFC — display: flow-root:
.bfc {
display: flow-root;
}
flow-root создаёт BFC без побочных эффектов вроде скролла или изменения inline-поведения.
Что даёт BFC:
- Изоляция float. Элементы внутри BFC не выходят за его границы, и сам BFC не обтекает float-соседей, если он блочный.
- Предотвращение схлопывания margin’ов между родителем и первым/последним потомком.
- Остановка обтекания float-элементов соседними блоками.
Inline Formatting Context (IFC)
IFC (строчный контекст форматирования) возникает внутри блочного контейнера, когда в нём находятся inline-элементы или текст. Элементы располагаются в строках, переносятся по white-space и word-break, и выравниваются по базовой линии.
Важные особенности IFC:
- Высота строки определяется
line-height, а не суммой высот inline-элементов. vertical-alignвлияет на положение inline-элемента относительно строки.- Блочные элементы внутри IFC прерывают его и создают анонимные блочные боксы.
Проблемы с IFC часто возникают, когда inline-элементы с разными font-size или vertical-align создают «лишнее» пространство под строкой. Это одна из причин, почему изображения внутри ссылок иногда имеют небольшой отступ снизу.
Flex и Grid Formatting Contexts (FFC, GFC)
FFC (flex-контекст форматирования) создаётся элементом с display: flex или display: inline-flex. Все прямые дети становятся flex-элементами и раскладываются по главной и поперечной осям.
GFC (grid-контекст форматирования) создаётся элементом с display: grid или display: inline-grid. Дети располагаются в ячейках сетки.
Общие особенности FFC и GFC:
- Flex/grid-элементы не обтекают float,
marginне схлопывается. floatиclearу flex-элементов не работают.z-indexработает у flex/grid-элементов даже безposition.- Размеры flex-элементов определяются не только
width/height, но иflex-basis/flex-grow/flex-shrink; в grid — треками сетки.
Containing block
Containing block (содержащий блок) — это прямоугольная область, относительно которой вычисляются размеры и позиция элемента. Для элементов в нормальном потоке containing block — это content-box ближайшего блочного предка. Но есть исключения:
- Для элемента с
position: fixedcontaining block — viewport. - Для элемента с
position: absolutecontaining block — ближайший позиционированный предок (неstatic). - Для элемента с
position: absolute, у которого предок имеетtransform,filter,perspectiveилиcontain: paint/layout, containing block может стать этот предок, даже если у негоposition: static.
.modal {
position: fixed;
inset: 0;
margin: auto;
width: 400px;
height: 200px;
}
Здесь inset: 0 растягивает элемент до границ viewport, а margin: auto центрирует его по размерам width/height. containing block — viewport.
Margin collapse
Margin collapse (схлопывание margin’ов) — одно из самых неочевидных поведений BFC. Вертикальные margin’ы соседних блочных элементов в одном BFC объединяются, и остаётся только больший из них.
Схлопываются:
- соседние блочные элементы;
- margin родителя и первого/последнего потомка, если между ними нет padding, border или BFC;
- пустые блочные элементы, если у них нет padding, border, height и min-height.
Не схлопываются:
- горизонтальные margin’ы;
- margin’ы элементов в разных BFC;
- margin’ы flex/grid-элементов;
- margin’ы элементов с
position: absolute/fixed; - margin’ы, у которых хотя бы один равен
auto.
/* Без схлопывания благодаря padding */
.card {
padding-top: 1px;
}
.card h2 {
margin-top: 24px;
}
Практические примеры
Пример 1: центрирование через position: absolute
<div class="overlay">
<div class="dialog">Dialog</div>
</div>
.overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
}
.dialog {
position: absolute;
inset: 0;
width: 400px;
height: 200px;
margin: auto;
}
inset: 0 растягивает .dialog до границ .overlay, а margin: auto центрирует его по заданным размерам.
Пример 2: z-index не пробивает родительский stacking context
<div class="parent-a">
<div class="child-a">A child</div>
</div>
<div class="parent-b">
<div class="child-b">B child</div>
</div>
.parent-a,
.parent-b {
position: relative;
z-index: 1;
}
.child-a {
position: absolute;
z-index: 9999;
}
.child-b {
position: absolute;
z-index: 2;
}
Если .parent-b в DOM идёт после .parent-a, он рисуется поверх него. child-a с z-index: 9999 всё равно останется под .parent-b, потому что он «заперт» внутри stacking context’а .parent-a.
Пример 3: sticky-шапка таблицы
<table>
<thead>
<tr><th>Name</th><th>Value</th></tr>
</thead>
<tbody>...</tbody>
</table>
thead th {
position: sticky;
top: 0;
background: white;
}
Шапка прилипает к верху viewport при прокрутке таблицы. Важно задать фон, иначе содержимое строк будет просвечивать сквозь шапку.
Пример 4: создание stacking context без побочных эффектов
.dropdown {
position: relative;
isolation: isolate;
}
.dropdown-menu {
position: absolute;
z-index: 10;
}
isolation: isolate создаёт новый stacking context, не добавляя трансформаций и не меняя прозрачность. Это полезно для компонентов вроде dropdown: меню будет рисоваться поверх соседей, но не выйдет за пределы своего компонента.
Пример 5: fixed внутри трансформированного контейнера
<div class="transformed">
<div class="fixed">Fixed</div>
</div>
.transformed {
transform: translateX(0);
}
.fixed {
position: fixed;
top: 0;
left: 0;
}
Несмотря на position: fixed, элемент .fixed будет позиционироваться относительно .transformed, а не viewport. Это одна из самых неприятных ловушек при работе с модальными окнами и поповерами.
Пример 6: BFC предотвращает обтекание float
<div class="media">
<img class="avatar" src="avatar.png" alt="">
<div class="content">
<h3>Title</h3>
<p>Description</p>
</div>
</div>
.avatar {
float: left;
width: 64px;
height: 64px;
margin-right: 16px;
}
.content {
display: flow-root; /* создаёт BFC */
}
.content образует BFC и перестаёт обтекать float-аватарку. Текст внутри не залезет под изображение.
Пример 7: схлопывание margin’ов и его предотвращение
<article>
<h2>Heading</h2>
<p>Paragraph</p>
</article>
article {
background: #f3f4f6;
}
h2 {
margin-top: 32px;
}
Без padding или border у article margin-top h2 «выпадет» за пределы article, и визуально отступ появится сверху article, а не между article и h2. Решения:
article {
background: #f3f4f6;
padding-top: 1px; /* или border-top, или display: flow-root */
}
Типичные ошибки и антипаттерны
- Большие значения
z-indexкак решение всех проблем. Если элемент не перекрывает соседа, чаще всего дело в stacking context’е, а не в недостаточномz-index. - Забытое
position: relativeу предка absolute-элемента. Элемент улетит к ближайшему позиционированному предку или к viewport. - Использование
z-indexбез позиционирования. Уposition: staticz-indexне работает. - Попытки вынести
fixed-элемент за пределы трансформированного предка. Любой предок сtransform/filter/perspective/containпревращается в containing block дляfixed. - Sticky, который не работает из-за
overflow. Если все предки имеютoverflow: hiddenбез прокрутки, sticky может не «прилипнуть». - Непонимание paint order. Даже без
z-indexбраузер рисует элементы в строгом порядке: фон, отрицательныйz-index, поток, float, inline, позиционированные элементы, положительныйz-index. - Использование
overflow: hiddenдля создания BFC. Работает, но может обрезать контент и тени.display: flow-root— лучший выбор. - Непонимание, почему margin «выпадает» из родителя. Выпадение margin — нормальное поведение BFC, а не баг. Лечится padding, border или
display: flow-root. - Попытка схлопнуть margin’ы в flex/grid. В flex- и grid-контекстах margin’ы не схлопываются — это ожидаемо.
Ключевые тезисы для интервью
positionбываетstatic,relative,absolute,fixed,sticky. Containing block дляabsolute— ближайший не-static предок или предок сtransform/filter/perspective/contain; дляfixed— обычно viewport, ноtransformу предка ломает это поведение.position: stickyтребует порога (top/bottom/left/right) и прокручиваемого предка; не работает, если предок имеетoverflow: hiddenбез прокрутки.- Stacking context — изолированная группа слоёв;
z-indexработает только внутри одного контекста. Дочерний элемент не может перекрыть элемент за пределами stacking context’а своего родителя, даже с огромнымz-index. Контекст создаютz-indexу позиционированного,opacity < 1,transform,filter,isolation: isolate, flex/grid-контейнер сz-indexу детей,contain: paint,will-change. - Порядок отрисовки внутри stacking context: фон контекста → отрицательный
z-index→ поток → float → inline → позиционированные → положительныйz-index. isolation: isolateсоздаёт stacking context без побочных эффектов — чистый способ изолировать наложение.- Formatting context — область с едиными правилами раскладки: BFC (блочный), IFC (inline), FFC (flex), GFC (grid). BFC создаётся через
display: flow-root,overflowнеvisible,float,position: absolute/fixedили flex/grid-контейнер;flow-root— современный способ без побочных эффектов. - Margin collapse работает только в BFC для соседних блоков и между родителем и крайними потомками; в flex/grid-контекстах margin’ы не схлопываются, а
z-indexработает безposition.
Заключение
Позиционирование в CSS определяется двумя ортогональными механизмами: containing block задаёт систему координат, а stacking context — порядок наложения. Ошибки с z-index почти всегда объясняются не недостаточным значением, а неожиданным stacking context’ом у предка. transform/filter у родителя ломают position: fixed, превращая его в absolute. Formatting contexts — основа предсказуемого поведения: BFC изолирует блоки, устраняет обтекание float и предотвращает выпадение margin’ов, а display: flow-root — современный способ его создать без побочных эффектов. Понимание порядка отрисовки и margin collapse помогает предсказывать, почему один элемент перекрывает другой и откуда берутся «неожиданные» отступы.