← назад к разделу

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

static и relative: точка отсчёта

static — умолчание: элемент стоит в потоке, свойства top/right/bottom/left игнорируются.

relative — элемент остаётся в потоке (его место сохраняется!), но визуально сдвигается относительно своей исходной позиции:

.badge-icon { position: relative; top: -2px; } /* приподнять на 2px */

Сдвиги relative используются редко. Настоящая работа relative другая — быть якорем: позиционированный элемент становится точкой отсчёта для absolute-потомков. position: relative без top/left — самая частая строчка позиционирования в реальном коде.

absolute: относительно кого?

absolute делает две вещи: выдёргивает элемент из потока (место схлопывается, соседи его больше не видят) и позиционирует относительно ближайшего позиционированного предка — первого родителя вверх по дереву, у которого position не static.

.card { position: relative; }        /* якорь */
.card .badge {
  position: absolute;
  top: 8px;
  right: 8px;                        /* правый верхний угол КАРТОЧКИ */
}

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

Полезная идиома — растянуть на весь якорь: inset: 0 (сокращение для top/right/bottom/left: 0) — оверлеи, подложки, кликабельные области поверх карточки. И заметьте: с заданными top+bottom или left+right absolute-элемент умеет растягиваться, а не только прибиваться к углу.

fixed и sticky: прибитые к экрану

fixed — как absolute, но точка отсчёта — окно браузера: элемент не прокручивается вместе со страницей. Шапки, плавающие кнопки, куки-баннеры. Из потока выдернут — контенту под фиксированной шапкой нужен компенсирующий отступ. Нюанс, который однажды сэкономит вам час: transform (а также filter) у предка превращает его в точку отсчёта для fixed — «фиксированный» элемент вдруг начинает скроллиться вместе с анимированным родителем.

sticky — гибрид: элемент едет в потоке как обычный, пока не доезжает до заданного порога — и прилипает:

.section-header {
  position: sticky;
  top: 0; /* порог обязателен — без него sticky не работает! */
}

В отличие от fixed, место в потоке сохраняется (ничего не перекрывается), и липнет элемент только в пределах своего родителя — доехал конец секции, заголовок уехал вместе с ней. Это и фича (заголовки таблиц, буквенные разделители списков), и источник главной загадки: «sticky не работает». Чек-лист: (1) задан ли top; (2) не является ли родитель ровно по высоте элемента — липнуть некуда; (3) нет ли на предках overflow: hidden/auto — они ломают прилипание.

z-index и контексты наложения

Кто кого перекрывает? Базово: позиционированные элементы выше обычных, при равенстве — позже в разметке значит выше. Точная настройка — z-index (работает только на позиционированных элементах и во flex/grid-детях).

Если бы всё было так просто, не существовало бы z-index: 999999. Настоящее правило: z-index сравнивается внутри одного контекста наложения (stacking context). Контекст создают: позиционированный элемент с z-index, opacity меньше 1, transform, filter, will-change и ещё десяток свойств. Внутри контекста дети сортируются между собой, но весь контекст целиком участвует во внешней сортировке как один слой:

<div class="dropdown-wrap"> <!-- opacity: 0.99 → создал контекст -->
  <div class="dropdown" style="z-index: 9999">...</div>
</div>
<div class="toolbar" style="position: relative; z-index: 10">...</div>

Дропдаун с z-index 9999 окажется под тулбаром с z-index 10: его девять тысяч действуют только внутри родительского контекста, а контекст-родитель против тулбара — слой без числа. Лечится не наращиванием нулей, а поиском какой предок создал контекст (DevTools в помощь) — либо переносом элемента наверх дерева (порталы модалок в React существуют ровно поэтому).

Гигиена: держите короткую шкалу слоёв проекта (например, 10 — липкие шапки, 100 — дропдауны, 1000 — модалки, 10000 — тосты) в CSS-переменных — и войн z-index не будет.

Коротко

  • relative — сдвиг от своего места и, главное, якорь для absolute-детей. Место в потоке сохраняет.
  • absolute — вне потока, отсчёт от ближайшего позиционированного предка. «Улетел» — ищи, где забыт relative. inset: 0 — растянуть на весь якорь.
  • fixed — прибит к окну; transform у предка ломает это. sticky — едет и прилипает в пределах родителя; нужен top, ломается overflow'ом предков.
  • z-index работает внутри контекста наложения; контексты создают opacity/transform/filter и позиционирование с z-index. «9999 не помогает» — виноват контекст предка.
  • Шкала слоёв в переменных проекта — прививка от войн z-index.

Что почитать дальше

  • Поток документа — что именно значит «выдернут из потока».
  • Flexbox — следующая тема: раскладка, в которой позиционирование нужно всё реже.
  • Анимации: CSS и оптимизации — transform, который создаёт контексты наложения, в своей главной роли.