Свойство position решает две вещи: остаётся ли элемент в потоке и относительно чего считаются top, right, bottom, left (вместе их называют inset-свойствами, есть сокращение inset).
Пять значений
| Значение | В потоке? | Относительно чего смещается |
|---|---|---|
static | да | не смещается, inset игнорируются |
relative | да, место сохраняется | своей обычной позиции |
absolute | нет | ближайшего позиционированного предка (containing block) |
fixed | нет | области просмотра (viewport) |
sticky | да | ведёт себя как relative, пока не упрётся в порог, затем «прилипает» внутри родителя |
static стоит по умолчанию. «Позиционированный» значит любой position, кроме static.
relative
Элемент сдвигается визуально, но его место в потоке остаётся, соседи не двигаются. Чаще всего relative нужен не для сдвига, а чтобы стать точкой отсчёта для абсолютных детей:
.card { position: relative; }
.badge { position: absolute; top: 8px; right: 8px; }
absolute и containing block
Абсолютный элемент выпадает из потока: родитель «не видит» его высоту, соседи занимают его место. Координаты считаются от padding-box ближайшего предка с position не static. Если такого нет, от начального containing block: прямоугольника размером с viewport в самом начале документа (поэтому при прокрутке такой элемент уезжает вместе со страницей).
Ловушка: containing block для absolute и fixed создаёт и предок с transform, filter, perspective, contain: paint или will-change: transform. Отсюда баг «модалка с position: fixed внутри анимированного блока ездит вместе с ним»: она позиционируется уже не от viewport. Лечится выносом модалки в конец body (в React через портал) или нативным <dialog>, который рисуется в верхнем слое (top layer).
Если задать и left, и right при width: auto, абсолютный элемент растянется между ними. inset: 0 растягивает его на весь containing block.
fixed
Как absolute, но отсчёт от viewport, поэтому элемент не уезжает при прокрутке. Подходит для плавающих кнопок, тостов, но не для шапки, которая должна сначала скроллиться: для этого sticky.
sticky
thead th { position: sticky; top: 0; }
Липкий элемент остаётся в потоке и ведёт себя как relative, пока при прокрутке не дойдёт до порога (top: 0). Дальше он прилипает, но только в пределах своего родителя: когда родитель уезжает, уезжает и он.
Почему sticky «не работает»:
- не задан порог (
top,bottomи т.д.); - у какого-то предка между элементом и прокручиваемой областью стоит
overflow: hiddenилиauto: тогда элемент липнет к этому предку, а он не скроллится; - родитель по высоте равен самому элементу, двигаться некуда.
z-index и контекст наложения
z-index задаёт порядок по оси Z, но работает только у позиционированных элементов, а также у flex- и grid-элементов (им position не нужен).
Главное понятие: контекст наложения (stacking context). Это изолированная «стопка»: элементы внутри сортируются только между собой, а наружу весь контекст выступает как одно целое со своим z-index. Его создают, например:
- корневой элемент
html; - позиционированный элемент с
z-indexнеauto; position: fixedиsticky;opacityменьше 1,transform,filter,isolation: isolate;- flex- или grid-элемент с
z-indexнеauto.
<div class="a" style="position:relative; z-index:1">
<div class="popup" style="position:absolute; z-index:9999">Я внутри a</div>
</div>
<div class="b" style="position:relative; z-index:2">Я выше всего блока a</div>
Попап с z-index: 9999 всё равно окажется под .b: он заперт в контексте .a с z-index: 1. Повышать число бессмысленно, надо поднимать сам контекст или выносить попап наружу.
Порядок внутри одного контекста снизу вверх: фон контекста, элементы с отрицательным z-index, обычные блоки в потоке, плавающие, строчные, позиционированные с z-index: auto или 0, затем положительные z-index. При равных значениях выше тот, кто позже в DOM.
isolation: isolate создаёт контекст без побочных эффектов: удобно, чтобы z-index внутри компонента не «протекал» наружу.
Логические свойства
Вместо top/left можно писать inset-block-start и inset-inline-start. Они учитывают направление письма: в арабском интерфейсе (dir="rtl") inset-inline-start окажется справа.
Частые ошибки
- Ставить
z-indexу элемента соstaticи ждать эффекта. - Поднимать
z-indexдо 99999 вместо того, чтобы разобраться в контекстах наложения. - Забыть
position: relativeу родителя и получить бейдж в углу страницы. - Ставить
overflow: hiddenна обёртку липкой шапки.
Что спросят на собесе
- Чем отличаются
relative,absolute,fixed,sticky? - Относительно чего позиционируется
absolute? - Почему
z-index: 9999не помогает поднять попап? - Что создаёт контекст наложения?
- Почему не работает
position: sticky?