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







