Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Властивість позиціонування CSS». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`position`** визначає, де елемент розміщується на сторінці і чи займає він місце в потоці документа. ```css .parent { position: relative; } /* контекст позиціонування */ .tooltip { position: absolute; top: 0; } /* прив'язується до .parent */ .navbar { position: fixed; top: 0; } /* залишається при прокрутці */ ``` **Ключове:** `static`/`relative` залишаються в потоці (місце зарезервоване). `absolute`/`fixed` виходять з нього (можуть перекривати). `sticky` поєднує обидва режими залежно від позиції прокрутки.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`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` ламається так, що на дебаг йде чимало часу.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.