Привет, Хабр!
position: sticky — штука, которая превращает relative-элемент в fixed-элемент, как только он доезжает до заданного инсет-порога, и отлипает в момент, когда скроллинг выталкивает родителя за край.
Работает круто, пока вы не включите overflow, не забудете задать top, не положите элемент в flex c align-items: stretch, не сделаете таблицу из <thead> и не упрётесь в кейс с вложенными скролл-контейнерами.
Что такое position: sticky и как движок принимает решение, липнуть ли элементу
Браузер хранит для каждого sticky-бокса sticky-constraint rectangle. Это прямоугольник, ограниченный ближайшим scroll container (предком с overflow ≠ visible) и инсет-значениями (top/bottom/left/right). Пока граница прямоугольника не пересекла вьюпорт, элемент ведёт себя как position: relative; после пересечения — переключается в fixed и пинится относительно того же контейнера, а не всего окна.
.sticky { position: sticky; top: 0; /* ← без этого инсет-триггера sticky не сработает */ z-index: 10; /* создаём новый stacking context, чтобы перекрывать контент */ }
Большинство не-прилипаний диагностируется одной строчкой — outline: 1px solid red; на подозреваемом контейнере. Обводка сразу показывает, к какому прямоугольнику элемент привязан.
Условия, без которых sticky не оживёт
|
№ |
Условие |
Как проверить |
Как починить |
|---|---|---|---|
|
1 |
Инсет задан ( |
DevTools > Computed > Position |
Добавьте |
|
2 |
Родитель не создаёт скролл-контейнер |
Ищите |
Уберите overflow, замените |
|
3 |
Высота родителя известна |
В Layout панели у div высота — |
Задайте |
|
4 |
Sticky меньше родителя |
Сравните bounding-rect в консоли |
Ограничьте |
|
5 |
Flex/Grid не ломает выравнивание |
flex-контейнер со |
Поставьте |
overflow: hidden создаёт невидимый scroll-context и ломает прилипания; спасение — overflow: clip или вынос sticky за пределы контейнера.
Что ж оно опять не прилипает? — короткий чек-лист:
// Вставьте в консоль DevTools [...document.querySelectorAll('.sticky')].forEach(el => { const chain = []; let p = el.parentElement; while (p) { chain.push([p.tagName, getComputedStyle(p).overflow]); p = p.parentElement; } console.table(chain, ['0','1']); // ищем overflow ≠ visible });
Overflow: враг липучести
<div class="wrapper"> <aside class="sidebar sticky">…</aside> <main>…длинный лонгрид…</main> </div>
.wrapper { overflow-x: hidden; /* Казалось бы, просто убираем горизонтальный скролл… */ } .sidebar { /* …но sticky больше не работает */ position: sticky; top: 0; }
Почему падает? overflow-x: hidden автоматически ставит overflow-y: auto. Браузер считает .wrapper новым scroll-container, и для него sticky порог никогда не достигается.
Фикс:
.wrapper { overflow: clip; } /* скрываем «мусор», но не создаём скролл */
или
/* Выносим липкую часть за «скрывающий» контейнер */ .layout { display: grid; grid-template-columns: 280px 1fr; }
Edge-кейсы
Таблицы
Есть спецификационная дыра: thead и tr не умеют position: relative, поэтому sticky нужно вешать напрямую на th.
<table> <thead> <tr> <th class="sticky">#</th> <th class="sticky">Название</th> … </tr> </thead> </table> th.sticky { position: sticky; top: 0; background: var(--bg); }
Если нужна залипающая первая колонка — комбинируйте position: sticky + left: 0 на каждом td/th целевого столбца.
Flexbox
align-items: stretch заставляет строку растянуться по высоте контента, и sticky просто не остаётся области, где прилипнуть. Решение — притянуть элемент к верху через align-self = flex-start и дать контейнеру фиксированную высоту.
.column { display: flex; flex-direction: column; height: 100vh; /* ← важно */ } .sidebar { align-self: flex-start; position: sticky; top: 0; }
Вложенные скролл-контейнеры
Когда sticky сидит внутри скролла в скролле, он приклеивается к ближайшему контейнеру. Часто приходится выбирать, где именно он должен зависнуть, и передавать событие прокрутки наружу. Быстрый костыль — связать скроллы через JS:
outer.addEventListener('scroll', e => inner.scrollLeft = e.target.scrollLeft);
Sticky vs Fixed: когда что брать в продакшен
|
Критерий |
|
|
|---|---|---|
|
Блок «отлипает» с родителем |
+ |
— |
|
Не блокирует событие scroll |
+ |
|
|
Не создаёт новый контекст наложения без |
+ |
-, |
|
Поддержка IE 11 |
— |
+ |
|
Требует указать inset |
+ |
Необязательно |
Fixed — это глобальная привязка к viewport. Sticky — контекстная, и в SPA-верстке это часто спасает от ручного расчёта высот.
Архитектура вертикальных срезов: как она помогает со sticky
Фронтенд больших продуктов давно перестал быть «шаблоном + немного jQuery». У каждой фичи — собственный state-менеджмент, micro-layouts и огороды из overflow. Если лепить sticky поверх слоёв, получаем хрупкие зависимости. Можно использовать Vertical Slice Architecture: ковыряемся не по слоям (controller → service → repo), а по фичам — slice на каждый use-case.
Что это даёт для sticky?
-
Изолированный scroll-context. У каждого slice свой layout-root, можно смело применять
overflowи не бояться поломать чужие sticky-шапки. -
Предсказуемая область прилипания. Slice знает, кто его родитель, и может гарантировать высоту контейнера.
-
Тестируемость. В E2E-тестах проще проверить, что
getBoundingClientRect().top === 0, когда slice развёрнут.
Мини-пример на React + CSS Modules
// slices/article/ArticlePage.tsx export default function ArticlePage() { return ( <section className={s.slice}> <TableOfContent className={s.toc}/> <article>{/* markdown */}</article> </section> ); }
/* slices/article/article.module.css */ .slice { display: grid; grid-template-columns: 240px 1fr; height: 100vh; /* ← высота замкнута внутри среза */ } .toc { position: sticky; top: calc(var(--header-h) + 16px); align-self: start; /* flex/grid safety */ }
Slice держит свои размеры и не зависит от глобального .wrapper{overflow-x:hidden} в другом модуле. Если захотим модалку поверх контента, просто создаём ещё один slice и не трогаем существующий.
Шпаргалка по Sticky
-
Проверяем тройку:
top,overflow,height. 90 % багов тут. -
Смотрим размер sticky против контейнера. Если больше — не пристанет.
-
В flex/grid дробим ось выравнивания.
align-self/justify-self. -
Нужен скролл-замок?
overflow: clipвместоhidden. -
Поддержка Safari ≤ 12? Двойная декларация
position: -webkit-sticky; position: sticky;. -
Сложный UI? Укладываем в Vertical Slice > изолируем layout.
Используйте вертикальные срезы, чтобы держать сложные интерфейсы под контролем, и не бойтесь комбинировать sticky с таблицами, flex’ом и grid’ом.
Если вы дочитали статью до конца — вы точно понимаете, что современная верстка это уже не «два div»а и float:left». Это архитектура компонентов, взаимодействие с движком браузера, баги из-за overflow и точечная отладка через DevTools.
Если вы хотите:
-
разобраться, почему CSS ведет себя «странно» — и как на самом деле работает модель раскладки;
-
научиться писать адаптивную, поддерживаемую верстку, а не просто «чтоб выглядело как в макете»;
то рекомендуем ознакомиться с программой курса HTML/CSS, который стартует 25 июня.
ссылка на оригинал статьи https://habr.com/ru/articles/912012/
Добавить комментарий