Как работает position: sticky и почему он часто не прилипает

Привет, Хабр!
position: sticky — штука, которая превращает relative-элемент в fixed-элемент, как только он доезжает до заданного инсет-порога, и отлипает в момент, когда скроллинг выталкивает родителя за край.
Работает круто, пока вы не включите Браузер хранит для каждого sticky-бокса sticky-constraint rectangle. Это прямоугольник, ограниченный ближайшим scroll container (предком с Большинство не-прилипаний диагностируется одной строчкой — № Условие Как проверить Как починить 1 Инсет задан ( DevTools > Computed > Position Добавьте 2 Родитель не создаёт скролл-контейнер Ищите Уберите overflow, замените 3 Высота родителя известна В Layout панели у div высота — Задайте 4 Sticky меньше родителя Сравните bounding-rect в консоли Ограничьте 5 Flex/Grid не ломает выравнивание flex-контейнер со Поставьте Что ж оно опять не прилипает? — короткий чек-лист: Почему падает? Фикс: или Есть спецификационная дыра: Если нужна залипающая первая колонка — комбинируйте Когда sticky сидит внутри скролла в скролле, он приклеивается к ближайшему контейнеру. Часто приходится выбирать, где именно он должен зависнуть, и передавать событие прокрутки наружу. Быстрый костыль — связать скроллы через JS: Критерий Блок «отлипает» с родителем + - Не блокирует событие scroll + Не создаёт новый контекст наложения без + -, Поддержка IE 11 - + Требует указать inset + Необязательно Fixed — это глобальная привязка к viewport. Sticky — контекстная, и в SPA-верстке это часто спасает от ручного расчёта высот. Фронтенд больших продуктов давно перестал быть «шаблоном + немного jQuery». У каждой фичи — собственный state-менеджмент, micro-layouts и огороды из Что это даёт для sticky? Изолированный scroll-context. У каждого slice свой layout-root, можно смело применять Предсказуемая область прилипания. Slice знает, кто его родитель, и может гарантировать высоту контейнера. Тестируемость. В E2E-тестах проще проверить, что Slice держит свои размеры и не зависит от глобального Проверяем тройку: Смотрим размер sticky против контейнера. Если больше — не пристанет. В flex/grid дробим ось выравнивания. Нужен скролл-замок? Поддержка Safari ≤ 12? Двойная декларация Сложный UI? Укладываем в Vertical Slice > изолируем layout. Используйте вертикальные срезы, чтобы держать сложные интерфейсы под контролем, и не бойтесь комбинировать sticky с таблицами, flex«ом и grid«ом. Если вы дочитали статью до конца — вы точно понимаете, что современная верстка это уже не «два div«а и float: left». Это архитектура компонентов, взаимодействие с движком браузера, баги из-заoverflow и точечная отладка через DevTools. Если вы хотите: • разобраться, почему CSS ведет себя «странно» — и как на самом деле работает модель раскладки; то рекомендуем ознакомиться с программой курса HTML/CSS, который стартует 25 июня.
Habrahabr.ru
прочитано 31033 раза
overflow, не забудете задать top, не положите элемент в flex c align-items: stretch, не сделаете таблицу из и не упрётесь в кейс с вложенными скролл-контейнерами.Что такое position: sticky и как движок принимает решение, липнуть ли элементу
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 не оживёт
top/left/...)top (или bottom/…) хотя бы 1pxoverflow: hidden/auto/scroll/clip у предковhidden → clip, или вынесите sticky наружуauto? height или min-heightmax-height, включите overflow на самом stickyalign-items: stretch? align-self: flex-start на stickyoverflow: 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: враг липучести
.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.
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;
}Вложенные скролл-контейнеры
outer.addEventListener('scroll', e => inner.scrollLeft = e.target.scrollLeft);Sticky vs Fixed: когда что брать в продакшен
position: stickyposition: fixedz-indexfixed всегда новый stacking contextАрхитектура вертикальных срезов: как она помогает со sticky
overflow. Если лепить sticky поверх слоёв, получаем хрупкие зависимости. Можно использовать Vertical Slice Architecture: ковыряемся не по слоям (controller → service → repo), а по фичам — slice на каждый use-case.overflow и не бояться поломать чужие sticky-шапки.getBoundingClientRect().top === 0, когда slice развёрнут.Мини-пример на React + CSS Modules
// slices/article/ArticlePage.tsx
export default function ArticlePage() {
return (
/* 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 */
}.wrapper{overflow-x:hidden} в другом модуле. Если захотим модалку поверх контента, просто создаём ещё один slice и не трогаем существующий.Шпаргалка по Sticky
top, overflow, height. 90% багов тут.align-self / justify-self.overflow: clip вместо hidden.position: -webkit-sticky; position: sticky;.
• научиться писать адаптивную, поддерживаемую верстку, а не просто «чтоб выглядело как в макете»;
