Властивість позиціонування CSS
position - це властивість CSS, яка визначає, де елемент розміщується на сторінці і чи бере він участь у нормальному потоці документа.
Теорія
TL;DR
- Нормальний потік схожий на призначені місця в театрі.
static- твоє місце.relativeтрохи зсуває тебе, але місце залишається зарезервованим.absoluteіfixedвиводять тебе на сцену повністю. staticіrelativeзберігають місце в потоці.absoluteіfixed- ні.relativeна батьківському елементі створює контекст позиціонування для дочірніх.absolute- для оверлеїв.fixed- для навбарів, які завжди на екрані.stickyведе себе якrelative, поки не досягне порогу прокрутки, після чого прилипає.
Швидкий приклад
.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 без позиціонованого батька
/* Неправильно: елемент прив'яжеться до body */
.element { position: absolute; top: 0; left: 0; }
/* Правильно: обгорнути в relative-контейнер */
.container { position: relative; }
.element { position: absolute; top: 0; left: 0; }Елемент підіймається по DOM, поки не знайде позиціонованого предка. Якщо не знайде - потрапляє до body. Майже ніколи не те, що потрібно.
z-index на статичному елементі
/* Неправильно: повністю ігнорується */
.card { z-index: 10; }
/* Правильно: спочатку потрібен контекст позиціонування */
.card { position: relative; z-index: 10; }z-index працює тільки на позиціонованих елементах. Без position - жодного ефекту.
fixed всередині overflow: hidden або overflow: auto
/* Неправильно: 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 можуть накладатись несподіваним чином.
Приклади
Базовий: п'ять значень поруч
<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
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
.container { height: 200vh; overflow: auto; }
.header { position: sticky; top: 0; background: #f1c40f; padding: 8px; }
.content { height: 100vh; padding: 16px; }<div class="container">
<div class="header">Sticky-заголовок</div>
<div class="content">Багато контенту...</div>
</div>Заголовок прилипає до краю прокрутки .container, а не viewport. Якщо цей .container потрапить всередину іншого елемента з overflow: hidden, заголовок взагалі перестане прилипати. Два рівні overflow і sticky ламається так, що на дебаг йде чимало часу.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.