Position Sticky в CSS: полное руководство по закреплению элементов на странице

Залипательное объяснение с примерами

Position Sticky в CSS: полное руководство по закреплению элементов на странице

position: sticky — это когда блок едет себе спокойно вместе со страницей, но доезжает до определённой точки и цепляется за экран, будто на липучке. Такое поведение отлично подходит для хедеров, боковых меню, шапок таблиц и других элементов, которые должны всегда быть перед глазами.

Сегодня на примерах узнаем, как с этим работать.

Что такое position: sticky в CSS

Основные принципы работы sticky-позиционирования

Обычно элементы располагаются в потоке документа (static по умолчанию) — браузер просто ставит их друг за другом, и каждый занимает своё место. Их можно сравнить с пассажирами в метро, которые сидят в вагоне: никто никого не трогает, каждый на своей позиции.

Но вот если элементу задать другое позиционирование — absolute или fixed, это будет похоже на то, как если бы пассажир встал и ушёл — пустое место тут же займут другие. Браузер рендерит таких «ушедших» отдельно, а потом накладывает их по определённым правилам: те, кто вне потока, толпятся у двери в порядке приоритета своего z-index.

Position Sticky в CSS: полное руководство по закреплению элементов на странице

А вот position: sticky — хитрый пассажир. Он сидит как все, но в нужный момент встаёт, берётся за поручень и идёт вдоль сидений (оставаясь в своём вагоне — родительском контейнере). Дойдя до конца, он снова садится на своё место, которое никто не занял, и продолжает путь вместе с остальными.

С точки зрения вёрстки sticky работает так:

  1. Элемент изначально находится в обычном потоке и занимает своё место в раскладке.
  2. Ему задают «точку прилипания» — одно из свойств top, bottom, left или right.
  3. При прокрутке, когда выбранный край элемента достигает этой точки (например, top: 16px), позиционирование переключается в режим фиксации относительно области прокрутки.
  4. Элемент остаётся закреплённым, пока не достигнет границы родительского контейнера — после этого он возвращается в поток и двигается вместе с остальным контентом.

Его место в потоке всегда сохраняется, поэтому раскладка не «скачет». Если же элемент должен перекрывать соседние блоки, это контролируется с помощью z-index.

Position Sticky в CSS: полное руководство по закреплению элементов на странице
Источник: abouolia.github.io

Отличие sticky от fixed и relative

sticky — особенный режим позиционирования, и понять его проще, если сравнить с другими.

relative

Элемент остаётся в потоке. Можно сместить его с помощью top/left, но место за ним в раскладке сохраняется. При прокрутке он двигается как обычный текст или картинка.

Здесь хедер в позиции relative — движется со всем документом в нормальном потоке:

Position Sticky в CSS: полное руководство по закреплению элементов на странице

fixed

Всегда закреплён относительно окна браузера. Не зависит от родителя, выпадает из потока, при прокрутке остаётся на месте. Идеально для шапок, баннеров или кнопок, которые должны всегда висеть в одном месте.

Здесь хедер в позиции fixed — прибит к верхней части:

Position Sticky в CSS: полное руководство по закреплению элементов на странице

sticky 

Сочетает поведение relative и fixed. Сначала элемент двигается вместе с остальной вёрсткой, как при относительном позиционировании, а в нужный момент фиксируется, как при фиксированном.

В этом примере липкая колонка слева сначала двигается нормально, но как только доходит до верха родительского блока, то прилипает к ней:

Position Sticky в CSS: полное руководство по закреплению элементов на странице

Как работает position sticky

Синтаксис и базовое применение

Чтобы элемент «прилип», достаточно прописать ему position: sticky и задать точку прилипания — например, top: 20px. Это значит, что как только верхняя граница элемента окажется в 20 пикселях от верхнего края окна, он перестанет двигаться и замрёт на этой позиции. До этого момента он ведёт себя как обычный блок в потоке. 

В CSS выглядит так:

.sidebar {
  position: sticky;
  top: 20px;
}

Какие элементы можно сделать sticky

Липким можно сделать практически любой блочный элемент: хедер, навигацию, боковое меню, подвал в таблице. Можно даже закрепить отдельную колонку в таблице, чтобы она всегда была на виду при горизонтальной прокрутке. 

Sticky имеет смысл применять к тем элементам, которые должны оставаться перед глазами при прокрутке. Например, важные кнопки — да, рекламный баннер, который отвлекает, — лучше не надо.

Область прилипания (sticky-контейнер)

Здесь кроется важный нюанс, о котором часто забывают. Sticky работает только в пределах своего родителя, который участвует в прокрутке. Этот родитель и есть sticky-контейнер. Как только липкий элемент доходит до нижней границы контейнера, он «отлипает» и едет вместе с остальным содержимым.

Position Sticky в CSS: полное руководство по закреплению элементов на странице

Например, если у вас есть <aside> внутри колонки, то контейнером для него будет сама колонка. Даже если страница прокручивается дальше, sticky не вылезет за её пределы. Это удобно, если нужно, чтобы сайдбар был закреплён только рядом с конкретным блоком, а не висел над футером или следующими секциями.

Проще всего запомнить так: sticky держится за границы ближайшего прокручиваемого родителя, а не за всю страницу.

Практические примеры использования

Рассмотрим три реальных сценария, где липкое позиционирование экономит нервы и избавляет от сложных костылей на JavaScript.

Закрепление шапки сайта при прокрутке

Классическая задача: при прокрутке хедер остаётся у верхней кромки экрана. Сделать это можно двумя способами — через fixed и через sticky.

С fixed элемент выпадает из потока и живёт поверх остального контента. Ширину он от родителя больше не берёт, поэтому, если хотите растянуть хедер на всю страницу, придётся явно задать left: 0; right: 0; top: 0;. Другой нюанс — контент под ним залезет наверх, так что придётся добавлять padding-top у следующего блока, чтобы ничего не перекрылось.

Пример с fixed:

Position Sticky в CSS: полное руководство по закреплению элементов на странице

Если явно не задать размеры, то элемент займёт только необходимую ширину.

А вот в случае sticky элемент всё ещё остаётся в потоке и живёт внутри своего родителя, поэтому ширина автоматически подстраивается под ширину контейнера. Не нужно задавать left/right, достаточно указать точку прилипания по оси прокрутки — обычно это верх:

header {
  position: sticky;
  top: 0;
}
Position Sticky в CSS: полное руководство по закреплению элементов на странице

В отличие от fixed, такой хедер будет липнуть только в пределах своего контейнера. Например, если на странице несколько секций с отдельными заголовками, каждый из них может иметь свой мини-хедер, который закрепляется только в пределах этой секции.

Липкое боковое меню

Боковое меню — другой частый кейс. Вы прокручиваете статью или документацию, а меню с якорями всегда рядом, чтобы быстро перескочить к нужному разделу. 

Допустим, у нас есть блок <main>, и внутрь него мы кладём <aside> и основной контент:

<main>
  <aside class="sidebar">
    <!-- меню -->
  </aside>
  <div class="content">
    <!-- текст статьи -->
  </div>
</main>

Чтобы колонки выстроились рядом, а не друг под другом, задаём флекс-раскладку для <main>:

main {
  /* включает флексбокс */
  display: flex;    
 /* отступ между колонками */
  gap: 20px;    
/* чтобы меню не тянулось по высоте контента */     
  align-items: flex-start; 
}
Теперь делаем меню липким:
.sidebar {
  position: sticky;
  top: 16px;
  /* фиксируем ширину колонки с меню */
  flex: 0 0 200px; 
}
.content {
 /* занимает всё оставшееся место */
  flex: 1;
}

Свойство top: 16px говорит браузеру: как только верхний край меню окажется в 16 пикселях от верхней границы области прокрутки, закрепи его там.

Меню липнет не ко всему документу, а к своему липкому контейнеру — ближайшему родителю, у которого можно прокручивать контент. В нашем примере это <main>. Пока контейнер выше меню, оно будет висеть у верхнего края экрана. Как только контейнер закончится — меню отлипнет и уедет вместе с остальным контентом.

Position Sticky в CSS: полное руководство по закреплению элементов на странице

👉 Важно, чтобы у контейнера, в котором находится меню, не было overflow: hidden или overflow: auto, иначе липкость не сработает как ожидается. Браузер будет считать такой контейнер отдельной зоной прокрутки, и sticky закрепится только внутри него. В итоге элемент либо вообще не залипнет, либо «отлипнет» слишком рано — у границы этого контейнера.

Прилипающие заголовки таблиц

Один из вопросов на собеседованиях: «Как сделать так, чтобы заголовок таблицы всегда был виден при прокрутке вниз и направо?». 

Логика такая: здесь мы специально ставим контейнеру с таблицей overflow: auto, чтобы прокрутка была не у всей страницы, а только у таблицы. Тогда sticky будет цепляться за границы именно этого контейнера: шапка прилипнет к его верху и поедет вниз только тогда, когда таблица полностью прокрутится.

<!-- Обёртка со скроллом — это наш липкий контейнер -->
<div class="table-wrapper">
  <!-- Внутрь кладём таблицу  -->
  <table>
    <thead>
      <tr>
        <!-- Первая ячейка пустая — это угол  -->
        <th></th>
        <!-- Остальные th будут липнуть только сверху -->
        <th>Колонка 1</th>
        <th>Колонка 2</th>
        <th>Колонка 3</th>
        <th>Колонка 4</th>
        <th>Колонка 5</th>
      </tr>
    </thead>
    <tbody>
      <!-- Данные неважны, нужны только строки для скролла -->
      <tr>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <!-- Дальше просто копипастим строки, пока не появится скролл -->
    </tbody>
  </table>
</div>

Дальше стилизуем эту разметку:

/* Липкий контейнер */
.table-wrapper {
/* ограничиваем высоту, чтобы был вертикальный скролл */
  max-height: 300px;   
 /* включаем прокрутку (и вертикаль, и горизонталь, если нужно) */
  overflow: auto;     
}
table {
/* убираем лишние зазоры между ячейками */
  border-spacing: 0;         
/* чтобы при узком контейнере был горизонтальный скролл */
  min-width: 600px;          
}
/* Заголовки колонок */
thead th {
  position: sticky;
/* Прилипаем к верху контейнера */
  top: 0;          
}
/* Первая колонка — липнем слева при горизонтальном скролле */
thead th:first-child,
tbody td:first-child {
  position: sticky;
 /* Цепляемся за левую границу контейнера */
  left: 0;        
}
Position Sticky в CSS: полное руководство по закреплению элементов на странице

В результате при прокрутке вниз заголовок таблицы остаётся на месте, а данные под ней двигаются. 

Особенности и ограничения

У свойства sticky есть свои ограничения, с которыми можно столкнуться в реальной вёрстке, особенно когда проект сложнее, чем одна колонка и хедер.

Поддержка браузерами и полифиллы

Поддержка position: sticky в современных браузерах почти полная — Chrome, Firefox, Safari, Edge дружат с ним давно. 

Position Sticky в CSS: полное руководство по закреплению элементов на странице

Проблемы начинаются только в старых версиях Safari на iOS и экзотических браузерах. 

Для таких случаев есть JS-полифиллы, которые имитируют липкость через прослушку события scroll: скрипт следит за положением элемента в окне и, когда он доходит до нужной точки, подменяет position: sticky на position: fixed с нужными координатами. При обратной прокрутке элемент «возвращают» в поток. 

Но в обычной фронтенд-практике сейчас можно спокойно использовать sticky без опаски, если вы не делаете продукт для древних устройств.

Работа с вложенными sticky-элементами

Иногда в одном контейнере нужно закрепить сразу несколько элементов. Например, у нас есть секция с заголовком и меню. Оба должны залипать, но меню должно появляться чуть ниже заголовка, а не налезать на него.

В таком случае мы делаем один липкий контейнер (сама секция) и два sticky-элемента внутри. Заголовок цепляем за верх (top: 0), а меню смещаем вниз на высоту заголовка (например, top: 40px).

Выглядит это так:

<section class="section">
  <header class="section-header">Заголовок секции</header>
  <nav class="section-menu">Меню</nav>
  <p>...много текста...</p>
</section>
И стилизация:
.section-header {
  position: sticky;
  top: 0;
}
.section-menu {
  position: sticky;
  /* прилипает чуть ниже заголовка */
  top: 40px; 
}
Position Sticky в CSS: полное руководство по закреплению элементов на странице

В итоге оба элемента липнут в пределах одной секции и не перекрывают друг друга. Как только секция уедет за экран, заголовок и меню отлипнут вместе с ней.

Взаимодействие с flex и grid

position: sticky спокойно работает в раскладках на flex и grid, но всегда важно помнить: если у контейнера стоит overflow: hidden, auto или scroll — липкость работать не будет.

В разделе про боковое меню мы уже делали липкий элемент во флекс-раскладке — там всё просто: меню залипает внутри своей колонки, а контент прокручивается.

С grid всё ещё проще — липкость работает в любой ячейке. Область прилипания при этом — сама ячейка или её трек (строка или колонка). То есть, если вы закрепили элемент в первом ряду, он будет прилипать в пределах этого ряда.

Допустим, мы хотим, чтобы при прокрутке страницы сверху всегда оставался хедер, а под ним — боковое меню, которое залипает чуть ниже. 

<div class="grid">
  <header class="header">Хедер</header>
  <aside class="sidebar">Меню</aside>
  <main class="content">Основной контент.</main>
</div>

Для этого делаем раскладку в две колонки: слева меню, справа контент, а сверху над ними — хедер на всю ширину.

.grid {
 /* Делаем грид-контейнером */
 display: grid;
 /* Определяем схему сетки:
    Первая строка — хедер на всю ширину (2 колонки)
    Вторая строка — слева меню (sidebar), справа контент */
 grid-template-areas:
   "header header"
   "sidebar content";
 /* Ширина колонок:
    первая — фиксированная 200px (для меню),
    вторая — занимает всё оставшееся место */
 grid-template-columns: 200px 1fr;
 /* Отступы между ячейками сетки */
 gap: 16px;
}
.content {
 /* Привязываем элемент к зоне content */
 grid-area: content;
}

Затем хедеру и меню задаём липкое позиционирование и разное значение top:

.header {
  grid-area: header;
  position: sticky;
  top: 0;
  background: white;
}
.sidebar {
  grid-area: sidebar;
  position: sticky;
/* чуть ниже хедера */
  top: 40px; 
  background: white;
}
Position Sticky в CSS: полное руководство по закреплению элементов на странице

В итоге при прокрутке хедер всегда остаётся вверху, а меню держится чуть ниже него. Как только колонка с меню заканчивается — оно уезжает вместе с остальным контентом.

Решение частых проблем

Sticky ломается чаще всего не из-за багов браузера, а из-за мелочей в вёрстке. Посмотрим, что стоит проверить в первую очередь.

Как задать точку «отлипания»

Чтобы липкий элемент вообще начал работать, ему нужно задать точку прилипания — свойство top (для прилипания сверху) или bottom (для снизу). Без этого браузер не поймёт, где его закрепить.

А уже то, где именно он отлипнет, зависит от нескольких факторов:

  • высоты контейнера — в маленьком контейнере отлипнет быстрее, в большом будет липким дольше;
  • значения top / bottom — меняют стартовую точку липкости;
  • отступов контейнера (padding-bottom, margin) — создают «воздушную подушку», из-за которой элемент отлипнет раньше.

Например, если у контейнера padding-bottom: 50px, липкий блок остановится за 50px до конца контейнера. Хотим, чтобы лип дольше, — убираем лишние отступы или увеличиваем высоту контейнера.

Почему sticky не работает (основные причины)

Если что-то не клеится, то проверьте, всё ли тут ок:

  • Не указана точка прилипания (top, bottom, left или right). Без неё вообще ничего не будет работать.
  • Родитель прокручивается (overflow: auto | hidden | scroll). Липкость работает в рамках ближайшего прокручиваемого контейнера. Если он маленький — элемент прилипнет и тут же отлипнет или не прилипнет вообще.
  • Контейнер ниже липкого элемента. Если элемент не помещается по высоте, липкости не будет.

Совместимость с transform и другими свойствами

Если на родителя липкого элемента повесить что-то типа transform: translateZ(0) или rotate(5deg), браузер создаёт для него свой отдельный слой. Липкость внутри всё ещё будет работать, но могут возникнуть странности:

  • Липкий блок вдруг уходит под другие элементы, даже с высоким z-index.
  • Пропадают тени (box-shadow) или фильтры.

То же самое будет со свойствами filter: blur(), perspective, backdrop-filter — все они тоже создают свой слой.

В примере ниже у красного блока с крестиком стоит transform, который создаёт свой контекст наложения. Из-за этого элемент оказывается поверх липкого хедера, хотя в коде идёт ниже. Фикс простой: дать хедеру больший z-index в рамках его слоя.

Position Sticky в CSS: полное руководство по закреплению элементов на странице

Если хотите, чтобы липкий элемент всегда был поверх — поднимите ему z-index и проверьте, чтобы у родителя не было overflow: hidden, которое может обрезать «вылезший» кусок.

Бонус для читателей

Скидка 20% на все курсы Практикума до 30 ноября! «Чёрная пятница» и такие скидки бывают раз в год.

Вам слово

Приходите к нам в соцсети поделиться своим мнением о статье и почитать, что пишут другие. А ещё там выходит дополнительный контент, которого нет на сайте — шпаргалки, опросы и разная дурка. В общем, вот тележка, вот ВК — велком!

Вам может быть интересно
easy