Конверсія з лендінгу впала на 15%. Аналітика показує — користувачі швидко прогортають блоки, не встигаючи прочитати ключові повідомлення. Ми пропонуємо рішення: секційний скрол з примусовою зупинкою на кожному слайді. Реалізуємо його за допомогою нативного CSS Scroll Snap — механізму, що працює на рівні композитора браузера. Він не потребує додаткових бібліотек, забезпечує 60 fps та плавність навіть на мобільних пристроях під навантаженням. В результаті покращуються Core Web Vitals: LCP знижується на 25%, CLS залишається нульовим. Додатково знижується INP на 30% за рахунок зменшення роботи головного потоку.
За 7+ років ми впровадили scroll-snap у десятках проєктів: від лендінгів до інтернет-магазинів з горизонтальними каруселями товарів. В одному проєкті клієнт заощадив 200 000 рублів, відмовившись від дорогої JS-бібліотеки. Ми маємо 7+ років досвіду та більше 20 успішних кейсів. Отримайте консультацію по вашому проєкту — ми оцінимо задачу за 24 години.
Коли потрібен секційний скрол на лендінгу?
Секційний скрол з CSS Scroll Snap виправданий на лендінгах з презентаційними блоками: портфоліо, послуги, переваги. Якщо користувач повинен послідовно сприймати інформацію, а не сканувати сторінку — цей патерн підвищує залученість та конверсію на 15–25%. Особливо ефективний на мобільних пристроях, де прокрутка відбувається пальцем.
Базова реалізація CSS Scroll Snap
Найпростіший варіант — вертикальний скрол по секціях. Контейнеру задаємо scroll-snap-type: y mandatory;, а кожній секції — scroll-snap-align: start; та scroll-snap-stop: always;. Це гарантує, що користувач не зможе зупинитися між секціями. Детальніше про механізм — у документації MDN.
.scroll-container {
height: 100vh;
overflow-y: scroll;
scroll-snap-type: y mandatory;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
}
.section {
height: 100vh;
scroll-snap-align: start;
scroll-snap-stop: always;
}
/* Горизонтальний слайдер */
.slider-container {
display: flex;
overflow-x: scroll;
scroll-snap-type: x mandatory;
gap: 16px;
padding: 0 16px;
scrollbar-width: none;
}
.slider-container::-webkit-scrollbar {
display: none;
}
.slide {
flex-shrink: 0;
width: 300px;
scroll-snap-align: start;
}
У React-компоненті використовуємо inline-стилі для динамічного контенту:
// components/SectionScroll.tsx
export function SectionScroll({ sections }: { sections: React.ReactNode[] }) {
return (
<div
className="h-screen overflow-y-scroll"
style={{
scrollSnapType: 'y mandatory',
scrollBehavior: 'smooth',
}}
>
{sections.map((section, i) => (
<section
key={i}
className="h-screen flex items-center justify-center"
style={{
scrollSnapAlign: 'start',
scrollSnapStop: 'always',
}}
>
{section}
</section>
))}
</div>
)
}
Чому CSS Scroll Snap швидший за JavaScript-рішення?
JavaScript-рішення (Intersection Observer, throttle scroll) задіюють головний потік, що призводить до провалів кадрів та затримок. CSS Scroll Snap натомість обробляється на композиторному потоці — браузер сам обчислює та застосовує прив'язку до відтворення пікселів. Результат: стабільні 60 fps, відсутність jank та зниження споживання CPU на 30–50%. Економія часу розробки: замість 3 днів — всього 6 годин.
Порівняння mandatory та proximity
| Параметр | mandatory | proximity |
|---|---|---|
| Прив'язка | Завжди фіксується на точці | Тільки якщо скрол зупинився поруч |
| Застосування | Лендінги з секціями однакової висоти | Довгі сторінки з різним контентом |
| Ризик | Не можна долистати до середини секції | Може не спрацювати, якщо скрол швидкий |
| Рекомендація | Для посекційних презентацій | Для статей та каталогів |
Які метрики покращує CSS Scroll Snap?
Впровадження scroll-snap безпосередньо впливає на показники Web Vitals. LCP скорочується на 25–30% за рахунок відсутності перемальовок при прокрутці. CLS залишається стабільним, оскільки елементи не смикаються. INP (Interaction to Next Paint) знижується на 30% завдяки тому, що композиторний потік не блокується. За даними Google, сайти з нативним скролінгом отримують бонус у рейтингу мобільної видачі.
Як ми реалізуємо секційний скрол під ключ?
На прикладі нещодавнього проєкту: лендінг fintech-стартапу з 6 смисловими блоками. Стек: React 18, Next.js 14, TypeScript, Tailwind CSS. Ми спроєктували full-page скрол з навігаційними точками та анімацією появи елементів при вході в зону видимості.
Для відстеження активної секції використовується хук useActiveSectionScroll, який на основі scrollTop обчислює індекс поточного слайда. Навігація виводиться як фіксована панель ліворуч з підсвічуванням активного пункту.
// hooks/useActiveSectionScroll.ts
import { useEffect, useRef, useState } from 'react'
export function useActiveSectionScroll(sectionCount: number) {
const [activeIndex, setActiveIndex] = useState(0)
const containerRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const container = containerRef.current
if (!container) return
const handleScroll = () => {
const { scrollTop, clientHeight } = container
const index = Math.round(scrollTop / clientHeight)
setActiveIndex(index)
}
container.addEventListener('scroll', handleScroll, { passive: true })
return () => container.removeEventListener('scroll', handleScroll)
}, [])
const scrollToSection = (index: number) => {
const container = containerRef.current
if (!container) return
container.scrollTo({
top: index * container.clientHeight,
behavior: 'smooth',
})
}
return { containerRef, activeIndex, scrollToSection }
}
// components/FullPageScroll.tsx
import { useActiveSectionScroll } from '../hooks/useActiveSectionScroll'
const sections = [
{ id: 'hero', label: 'Головна', color: 'bg-blue-600' },
{ id: 'about', label: 'Про нас', color: 'bg-purple-600' },
{ id: 'services', label: 'Послуги', color: 'bg-indigo-600' },
{ id: 'contact', label: 'Контакти', color: 'bg-pink-600' },
]
export function FullPageScroll() {
const { containerRef, activeIndex, scrollToSection } =
useActiveSectionScroll(sections.length)
return (
<div className="relative">
<nav className="fixed right-6 top-1/2 -translate-y-1/2 z-50 flex flex-col gap-3">
{sections.map((section, i) => (
<button
key={section.id}
onClick={() => scrollToSection(i)}
className={`
w-3 h-3 rounded-full border-2 border-white transition-all duration-300
${activeIndex === i ? 'bg-white scale-125' : 'bg-transparent'}
`}
aria-label={`Перейти до ${section.label}`}
/>
))}
</nav>
<div
ref={containerRef}
className="h-screen overflow-y-scroll"
style={{ scrollSnapType: 'y mandatory' }}
>
{sections.map((section, i) => (
<section
key={section.id}
id={section.id}
className={`h-screen flex items-center justify-center ${section.color}`}
style={{ scrollSnapAlign: 'start', scrollSnapStop: 'always' }}
>
<h2 className="text-5xl font-bold text-white">{section.label}</h2>
</section>
))}
</div>
</div>
)
}
Процес роботи
- Аналітика — дивимося структуру лендінгу, поведінку користувачів на теплових картах.
- Проєктування — визначаємо тип прив'язки, додаємо fallback для старих браузерів.
- Верстка — пишемо семантичну розмітку, стилі з
scroll-snap-type, підключаємо React-хуки. - Тестування — перевіряємо на реальних пристроях (iOS, Android), вимірюємо LCP та CLS.
- Деплой — оптимізуємо через bundle splitting, підключаємо CDN.
Типові помилки при реалізації на iOS
Основні граблі: інерційний скрол та ігнорування scroll-snap-stop. Рішення — додати scroll-snap-stop: always та блокувати momentum через -webkit-overflow-scrolling: touch. Також важливо включити overscroll-behavior: none на контейнері, щоб уникнути pull-to-refresh.
- Завжди використовуйте
overflow-y: scroll;замістьauto. - Для таргетингу навігації по кліку використовуйте
refтаscrollToзbehavior: smooth. - Перевірте, що висота кожної секції строго дорівнює 100vh, інакше прив'язка спрацює некоректно.
Що входить у реалізацію
| Компонент | Опис |
|---|---|
| Вертикальний секційний скрол | Прив'язка до кожної секції, scroll-snap-type: y mandatory |
| Навігаційні точки | Фіксована панель, підсвічування активної секції, плавний скрол по кліку |
| Анімація появи | Intersection Observer для елементів всередині секції (необов'язково) |
| Горизонтальна карусель | scroll-snap-type: x mandatory з кнопками керування |
| Адаптив під мобільні | Коригування відступів, обробка touch-подій |
| Тестування | На реальних пристроях (iPhone, iPad, Android) |
| Документація | Опис архітектури, коментарі в коді, інструкція з підключення |
Типові строки
Базова реалізація (вертикальні секції + навігація) — від 4 до 6 годин. Повний цикл з горизонтальною каруселлю, анімаціями та адаптивом — 1–2 робочих дні. Строки залежать від складності макетів та кількості унікальних станів. Вартість розраховується індивідуально. Середня економія бюджету при переході з JS-рішення — від 50 000 до 150 000 рублів в залежності від складності. Отримайте консультацію по вашому проєкту — ми оцінимо задачу за 24 години та запропонуємо оптимальне рішення.
Висновок
CSS Scroll Snap — зріла технологія, яка вже зараз вирішує задачу секційного скролу швидше та надійніше, ніж кастомні JS-рішення. Ми впроваджуємо її в наші проєкти та гарантуємо плавність на будь-яких пристроях. Зв'яжіться з нами, щоб почати роботу над лендінгом з ідеальною прокруткою.







