Уявіть: інтернет-магазин з 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 місяців.







