Skip to main content

Властивість позиціонування CSS

position - це властивість CSS, яка визначає, де елемент розміщується на сторінці і чи бере він участь у нормальному потоці документа.

Теорія

TL;DR

  • Нормальний потік схожий на призначені місця в театрі. static - твоє місце. relative трохи зсуває тебе, але місце залишається зарезервованим. absolute і fixed виводять тебе на сцену повністю.
  • static і relative зберігають місце в потоці. absolute і fixed - ні.
  • relative на батьківському елементі створює контекст позиціонування для дочірніх. absolute - для оверлеїв. fixed - для навбарів, які завжди на екрані.
  • sticky веде себе як relative, поки не досягне порогу прокрутки, після чого прилипає.

Швидкий приклад

css
.container { position: relative; /* створює контекст позиціонування */ height: 100px; } /* Залишається в потоці, ігнорує top/left */ .static-box { position: static; } /* Зсувається на 20px вниз, але залишає порожнє місце */ .nudged { position: relative; top: 20px; } /* Виходить з потоку, прив'язується до .container */ .overlay { position: absolute; top: 10px; left: 10px; }

static - значення за замовчуванням. Будь-яке інше значення активує offset-властивості (top, left тощо) і змінює спосіб розрахунку позиції елемента.

Головна різниця

static і relative залишаються в потоці, тому сусідні елементи виділяють для них місце. Зсунь relative-елемент на 50px вниз - над ним з'явиться помітна порожнина. absolute і fixed повністю виходять з потоку: вони зависають над сторінкою, а сусідні елементи поводяться, ніби їх немає. sticky починає як relative і змінює поведінку, щойно досягає вказаного краю прокрутки.

Коли що використовувати

  • Звичайне розміщення без зміщень: static
  • Невеликий візуальний відступ без порушення потоку: relative
  • Контейнер, до якого прив'язуються дочірні елементи: relative на батьківському
  • Тултіп, дропдаун, бейдж або модалка: absolute всередині relative-обгортки
  • Навбар або банер, що залишається на екрані при прокрутці: fixed
  • Заголовок таблиці або сайдбар, що прокручується з контентом і потім прилипає: sticky

Таблиця порівняння

ЗначенняУ потоці?Точка відлікуtop/left працюють?Прокручується?Для чого
staticТакНормальний потікНіТакЗвичайний лейаут
relativeТакВласна вихідна позиціяТакТакНевеликі зсуви, контекст для дочірніх
absoluteНіНайближчий позиціонований предок або bodyТакТакОверлеї, дропдауни, бейджі
fixedНіViewportТакНіНавбари, банери, тости
stickyТак (до порогу)Потік, потім край прокруткиТакНі (на краю)Заголовки таблиць, сайдбари

Як браузер розраховує позицію

Під час рендерингу браузер будує дерево лейауту. static і relative елементи отримують координати всередині нормального блочного або рядкового контексту. absolute і fixed формують окремий containing block: рушій піднімається по ланцюжку предків і шукає перший елемент зі значенням position, відмінним від static. Для fixed точкою відліку стає viewport, якщо такого предка немає.

sticky працює інакше. Він живе в потоці, і браузер відстежує його позицію під час прокрутки. Щойно offset збігається з порогом, елемент прилипає до краю контейнера прокрутки. Одна неочевидна деталь: якщо scroll-контейнер має overflow: auto або overflow: hidden, sticky прилипає до нього, а не до viewport. Бачив, як це дивує команди, що очікували поведінки fixed всередині прокручуваного div.

z-index діє тільки на позиціоновані елементи (все, крім static). z-index: 10 на статичному елементі просто нічого не зробить.

Типові помилки

absolute без позиціонованого батька

css
/* Неправильно: елемент прив'яжеться до body */ .element { position: absolute; top: 0; left: 0; } /* Правильно: обгорнути в relative-контейнер */ .container { position: relative; } .element { position: absolute; top: 0; left: 0; }

Елемент підіймається по DOM, поки не знайде позиціонованого предка. Якщо не знайде - потрапляє до body. Майже ніколи не те, що потрібно.

z-index на статичному елементі

css
/* Неправильно: повністю ігнорується */ .card { z-index: 10; } /* Правильно: спочатку потрібен контекст позиціонування */ .card { position: relative; z-index: 10; }

z-index працює тільки на позиціонованих елементах. Без position - жодного ефекту.

fixed всередині overflow: hidden або overflow: auto

css
/* Неправильно: fixed-елемент обрізається батьківським контейнером */ .panel { overflow: auto; } .toast { position: fixed; bottom: 20px; right: 20px; } /* Правильно: винести fixed-елемент за межі overflow-контейнера */ body > .toast { position: fixed; bottom: 20px; right: 20px; }

transform, filter або will-change на будь-якому предку також створюють новий containing block і непомітно ламають fixed-позиціонування.

sticky без висоти у батьківського контейнера

Якщо батько стискається або не має висоти, sticky не має де прокручуватись і ніколи не прилипне. Батьківський контейнер має бути достатньо високим, щоб прокрутка взагалі відбувалась.

Реальне застосування

  • Модальні вікна в React: зовнішній div з position: fixed перекриває весь viewport (top: 0, left: 0, right: 0, bottom: 0, zIndex: 1000), внутрішній div з position: absolute центрується через transform: translate(-50%, -50%)
  • Material UI AppBar: position: fixed за замовчуванням, контент отримує відповідний margin-top
  • Bootstrap бейдж на кнопці: position: relative на кнопці, position: absolute на бейджі з top: -8px, right: -8px
  • Таблиці Headless UI / Tailwind: sticky на thead th для заголовків прокручуваних таблиць
  • Сайдбар VS Code: fixed всередині Electron viewport

Питання на співбесіді

Q: Яка різниця між fixed і sticky?
A: fixed завжди прив'язаний до viewport незалежно від прокрутки. sticky спочатку живе в нормальному потоці, а потім прилипає до краю прокрутки після досягнення порогу. На відміну від fixed, він перестає рухатись, коли батьківський контейнер виходить з поля зору.

Q: Що створює containing block для absolute?
A: Будь-який предок з position, відмінним від static. Також: предок з transform, filter, perspective, will-change: transform або contain: layout. Саме з цього виникають баги "мій fixed-елемент чомусь не фіксований".

Q: Чому z-index не працює на моєму елементі?
A: Дві типові причини. Перша: елемент має position: static, постав будь-яке інше значення. Друга: елемент знаходиться всередині stacking context (контексту нашарування) з нижчим z-index, ніж у сусіднього контексту. Батько з opacity < 1, transform або isolation: isolate створює окремий контекст, і дочірні елементи не можуть вийти за його межі.

Q: Якщо використовую відсоткові зсуви з absolute, відносно чого рахується розмір?
A: Відносно розміру containing block, а не контентної зони батька. Тобто top: 50% - це 50% висоти найближчого позиціонованого предка.

Q: Чи працює sticky всередині flexbox або grid?
A: Так, але вісь прилипання залежить від напрямку прокрутки. align-self: stretch (за замовчуванням у flex) може впливати на момент досягнення порогу.

Q: Поясни stacking contexts і порядок відмальовування.
A: Stacking context - це ізольований шар у дереві рендерингу. Він створюється через position: relative/absolute з ненульовим z-index, opacity < 1, transform, filter, isolation: isolate та інші. Всередині контексту дочірні елементи малюються за порядком z-index. Але дитина не може вийти за межі батьківського контексту: якщо два контексти є сусідами, той, чий батько має вищий z-index, завжди малюється зверху - незалежно від значень всередині. Тому дві модалки з різних частин DOM можуть накладатись несподіваним чином.

Приклади

Базовий: п'ять значень поруч

html
<style> .container { position: relative; height: 120px; background: #e8f4f8; } .box { padding: 4px 8px; font-size: 14px; } .static-box { background: #e74c3c; } .relative-box { background: #2ecc71; position: relative; top: 20px; } .absolute-box { background: #e67e22; position: absolute; top: 8px; right: 8px; } </style> <div class="container"> <div class="box static-box">static: звичайне місце</div> <div class="box relative-box">relative: прогалина 20px зверху</div> <div class="box absolute-box">absolute: правий верхній кут контейнера</div> </div>

Червоний блок стоїть на своєму природному місці. Зелений зсувається вниз і залишає порожнину. Помаранчевий повністю виходить з потоку і прив'язується до кута контейнера.

Середній: модальне вікно в React

jsx
function Modal({ isOpen, children }) { if (!isOpen) return null; return ( // Перекриває весь viewport, блокує взаємодію зі сторінкою <div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.5)', zIndex: 1000 }}> {/* Центрує діалог незалежно від його розміру */} <div style={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', background: '#fff', padding: '24px', borderRadius: '8px' }}> {children} </div> </div> ); }

Зовнішній fixed-шар залишається на екрані під час прокрутки і блокує сторінку. Внутрішній absolute-шар використовує transform для центрування незалежно від розміру діалогу.

Просунутий: пастка зі sticky та overflow

css
.container { height: 200vh; overflow: auto; } .header { position: sticky; top: 0; background: #f1c40f; padding: 8px; } .content { height: 100vh; padding: 16px; }
html
<div class="container"> <div class="header">Sticky-заголовок</div> <div class="content">Багато контенту...</div> </div>

Заголовок прилипає до краю прокрутки .container, а не viewport. Якщо цей .container потрапить всередину іншого елемента з overflow: hidden, заголовок взагалі перестане прилипати. Два рівні overflow і sticky ламається так, що на дебаг йде чимало часу.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?