Уявіть: інтернет-магазин з 50 000 товарів. Sticky-хедер при скролі займає 10% екрану, погіршуючи LCP на 1.5 секунди і збільшуючи CLS до 0.25. На мобільних кнопка «Купити» перекривається нижньою навігацією без safe-area. Такі проблеми вирішуються production-ready кодом, що пройшов перевірку на сайтах зі 100k+ сесій на день. Ми, команда з 10+ років досвіду у веб-розробці, реалізували sticky-елементи для більш ніж 50 проєктів. Розберемо реалізацію sticky header/footer з hide-on-scroll, React-хуком і підтримкою iOS. Якщо вам потрібне готове рішення — замовте його під ключ з гарантією 6 місяців.
Як працює position: sticky?
position: sticky — базовий інструмент, але він не без нюансів. Батьківський контейнер не повинен мати overflow: hidden, інакше прилипання не спрацює. Властивість не підтримує «втечу» при скролі вниз і може конфліктувати з кастомними відступами. Для простих сценаріїв — ок, для складних — ні. Згідно з MDN, position: sticky — це гібрид relative і fixed.
Базовий CSS для sticky header
/* Стили для sticky header */
.site-header {
position: sticky;
top: 0;
z-index: 100;
background: #fff;
will-change: transform;
transition: box-shadow 0.2s ease;
}
.site-header--scrolled {
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1);
}
/* Hide-on-scroll: приховування при прокрутці вниз */
.site-header--hidden {
transform: translateY(-100%);
transition: transform 0.3s ease;
}
/* Sticky footer з safe-area */
.bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 100;
background: #fff;
border-top: 1px solid #e2e8f0;
padding-bottom: env(safe-area-inset-bottom);
padding-bottom: max(env(safe-area-inset-bottom), 8px);
}
/* Sidebar з урахуванням висоти хедера */
:root { --header-height: 64px; }
.sidebar {
position: sticky;
top: calc(var(--header-height) + 24px);
max-height: calc(100vh - var(--header-height) - 48px);
overflow-y: auto;
overscroll-behavior: contain;
}
/* Anchor-посилання: scroll-margin-top */
[id] {
scroll-margin-top: calc(var(--header-height, 64px) + 16px);
}
Чому transform ефективніший для hide-on-scroll?
Зміна transform не викликає reflow — браузер обробляє його на GPU. Це в 10+ разів швидше, ніж модифікація top або height. Патерн hide-on-scroll: приховуємо хедер при прокрутці вниз, показуємо при прокрутці вгору. Використовуємо throttling через requestAnimationFrame для мінімізації навантаження. Типова реалізація на нативному JavaScript:
const header = document.querySelector('.site-header');
let lastScrollY = 0;
let scrollStart = 0;
let hidden = false;
const THRESHOLD = 100;
const HIDE_AFTER = 50;
function onScroll() {
const y = window.scrollY;
const diff = y - lastScrollY;
if (diff > 0) {
if (!hidden && y > THRESHOLD && y - scrollStart > HIDE_AFTER) {
header.classList.add('site-header--hidden');
hidden = true;
}
} else {
if (hidden) {
header.classList.remove('site-header--hidden');
hidden = false;
scrollStart = y;
}
}
lastScrollY = y;
}
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => { onScroll(); ticking = false; });
ticking = true;
}
}, { passive: true });
Як реалізувати hide-on-scroll з React-хуком?
На React 18+ використовуємо налаштовуваний хук, що повертає стани isScrolled і isVisible. Це в 3–5 разів зменшує кількість перемальовувань порівняно з прямою маніпуляцією DOM. Ось повний приклад хука та компонента:
import { useState, useEffect, useRef } from 'react';
interface StickyHeaderState {
isScrolled: boolean;
isVisible: boolean;
scrollY: number;
}
function useStickyHeader(hideOnScrollDown = true): StickyHeaderState {
const [state, setState] = useState<StickyHeaderState>({ isScrolled: false, isVisible: true, scrollY: 0 });
const lastScrollY = useRef(0);
const scrollStart = useRef(0);
useEffect(() => {
let ticking = false;
const handler = () => {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
const y = window.scrollY;
const diff = y - lastScrollY.current;
setState(prev => {
let isVisible = prev.isVisible;
if (hideOnScrollDown) {
if (diff > 0 && y > 100 && y - scrollStart.current > 50) {
isVisible = false;
} else if (diff < 0) {
if (!isVisible) scrollStart.current = y;
isVisible = true;
}
}
return { isScrolled: y > 10, isVisible, scrollY: y };
});
lastScrollY.current = y;
ticking = false;
});
};
window.addEventListener('scroll', handler, { passive: true });
return () => window.removeEventListener('scroll', handler);
}, [hideOnScrollDown]);
return state;
}
export function SiteHeader() {
const { isScrolled, isVisible } = useStickyHeader(true);
return (
<header
className={[
'site-header',
isScrolled ? 'site-header--scrolled' : '',
!isVisible ? 'site-header--hidden' : '',
].filter(Boolean).join(' ')}
>
{/* контент */}
</header>
);
}
Як уникнути перекриття контенту sticky-хедером при якірних посиланнях?
Додайте CSS-властивість scroll-margin-top усім елементам з id: scroll-margin-top: calc(var(--header-height) + 16px). Або використовуйте JS для обчислення offset і ручного scrollTo при кліку на якірні посилання. Без цього якорі будуть ховатися під хедер, що погіршує UX.
Що таке safe-area-inset-bottom і як його використовувати?
Для мобільної навігації знизу використовуємо position: fixed з урахуванням env(safe-area-inset-bottom). У <head> додайте <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">. Компенсуйте висоту в контенті: padding-bottom: calc(60px + env(safe-area-inset-bottom)). Це запобігає перекриттю контенту системними панелями iOS.
Що входить у роботу?
- Аудит поточної реалізації sticky-елементів з виміром Core Web Vitals.
- Розробка кастомного рішення на вашому стеку (React, Vue, Angular або нативний JS).
- Інтеграція з існуючою архітектурою, включаючи SSR та CSRF-захист.
- Оптимізація продуктивності: усунення layout thrashing, мінімізація reflow.
- Тестування на реальних пристроях (iPhone, Android, iPad) з різними версіями браузерів.
- Надання документації та навчання вашого розробника.
- Пост-релізна підтримка протягом 6 місяців.
Підтримка браузерів
Sticky-елементи підтримуються у всіх сучасних браузерах (Chrome 56+, Firefox 59+, Safari 8+, Edge 16+). Для IE11 потрібен поліфіл. Safe-area підтримується у Safari починаючи з iOS 11.Порівняння підходів: sticky vs fixed
| Параметр | position: sticky | position: fixed |
|---|---|---|
| Вбудовування в потік | Так | Ні |
| Займає місце | Так | Ні |
| Прилипання при скролі | Будь-який напрямок | Фіксоване положення |
| Продуктивність | Висока | Висока |
| Підтримка hide-on-scroll | Потрібен JS | Потрібен JS |
Типові помилки при реалізації sticky-елементів
| Проблема | Рішення |
|---|---|
Батьківський контейнер має overflow: hidden |
Приберіть overflow: hidden або змініть структуру |
Не налаштований z-index |
Задайте z-index: 100 для sticky-елемента |
Відсутній will-change: transform |
Додайте will-change: transform для продуктивності |
Не врахована safe-area-inset-bottom на мобільних |
Використовуйте env(safe-area-inset-bottom) у footer |
Якірні посилання без scroll-margin-top |
Додайте scroll-margin-top усім елементам з id |
Використання top або height для анімації приховування |
Використовуйте transform: translateY() |
Процес роботи
- Аналітика — аудит поточної поведінки та Core Web Vitals. Вимірюємо LCP, CLS, INP.
- Проектування — вибір підходу (sticky/hide-on-scroll), врахування mobile-first.
- Реалізація — написання коду з інтеграцією у ваш проект. Використовуємо сучасний стек (React 18, TypeScript, CSS Modules).
- Тестування — перевірка на реальних пристроях (iPhone, Android, iPad), виміри метрик.
- Деплой — передача коду, документація, навчання вашого розробника.
Терміни та вартість
Термін реалізації — від 3 до 7 робочих днів залежно від складності. Вартість розраховується індивідуально. Ви економите на виправленні помилок, які виникли б при самостійній реалізації. Зв'яжіться з нами для оцінки вашого проєкту — отримайте консультацію та попередній розрахунок протягом одного дня. Замовте реалізацію sticky-елементів під ключ з гарантією 6 місяців.







