Ви відправили в продакшн «прості» горизонтальні галереї, а тепер у службі підтримки скарги, що вони «заїдають», «стрибають», «пожирають прокрутку» або «ламаються на iPad». Продукт хоче «маслянистої» поведінки. Доступність хоче підтримки клавіатури. Маркетинг хоче, щоб це виглядало як карусель, але «без JS, будь ласка».
Добра новина: CSS scroll snapping може вирішити більшість цих завдань — якщо ви проєктуєте його так, як керуєте продакшн-системами: передбачувано, спостережливо і витривало до поганих вхідних даних (наприклад, 14 МБ зображень і вкладених переповнень). Погана новина: за допомогою нього легко зробити галерею, яка працює на вашому ноутбуці і псує життя всім іншим.
Що таке scroll snap насправді (а чого це не стосується)
Scroll snapping — це не «компонент каруселі». Це функція браузера, яка схиляє контейнер прокрутки зупинятися у визначених точках прив’язки (snap points). Ви даєте підказки для фізики; UA вирішує, як приземлитися. Це важлива відмінність, бо ви не можете командувати цим так, як JS. Ви лише встановлюєте обмеження й дозволяєте браузеру домовлятися з пристроями вводу, налаштуваннями доступності та намірами користувача.
У практичних термінах:
scroll-snap-typeзастосовується до контейнера прокрутки. Він оголошує «прив’язку по осі x» і наскільки строго її застосовувати.scroll-snap-alignзастосовується до дочірніх елементів. Він вказує, де кожен елемент хоче вирівнятися відносно snapport контейнера.scroll-paddingіscroll-margin— це коригування «так, у нас є липкий хедер».scroll-behavior: smoothвпливає на програмну прокрутку (і на деякі UA-поведінки), а не на голу фізику дотику.
Базова думка: ставтеся до scroll-snap як до прогресивного покращення. Галерея має бути корисною як проста горизонтальна прокрутка, навіть якщо прив’язка вимкнена або ігнорується.
Дві речі, що scroll snap не є:
- Воно не детерміноване. Якщо ви тестуєте на трекпаді, ви помітите інше посаджування snap, ніж на мишці, і ще інше на дотику. Це не баг; це особливість потоку вводу.
- Воно не замінює навігаційні контролі. Деяким користувачам потрібні явні кнопки «далі/назад». «Без JS» не означає «без контролів»; це значить, що ви будете використовувати анкори, фокус і розумну верстку.
Одна перефразована ідея з інженерної культури, яка тут доречна (і є причиною, чому ми вимірюємо): «Надія — це не стратегія.» Якщо ваша галерея «зазвичай відчувається нормально», ви ще не тестували режими відмови.
Мінімальна scroll-snap галерея, яку можна випустити
Випускайте найменшу річ, яка працює, потім зміцнюйте її. Ось базова структура, що підтримує дотик/трекпад прокрутку, точки прив’язки, фокусовані елементи й стабільну верстку.
cr0x@server:~$ cat gallery.html
<style>
.gallery {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 80%;
gap: 16px;
overflow-x: auto;
overscroll-behavior-x: contain;
scroll-snap-type: x mandatory;
scroll-padding-inline: 16px;
padding: 16px;
border: 1px solid #e6e6e6;
border-radius: 12px;
}
.card {
scroll-snap-align: start;
scroll-snap-stop: normal;
border-radius: 12px;
border: 1px solid #e6e6e6;
background: #fff;
min-height: 220px;
/* make focus obvious without being ugly */
outline: none;
}
.card:focus-visible {
box-shadow: 0 0 0 3px rgba(11,87,208,0.35);
}
/* reduce accidental vertical scroll chaining on touch */
.gallery { touch-action: pan-x; }
/* motion respect */
@media (prefers-reduced-motion: reduce) {
.gallery { scroll-behavior: auto; }
}
/* widen on bigger screens */
@media (min-width: 900px) {
.gallery { grid-auto-columns: 45%; }
}
</style>
<div class="gallery" aria-label="Featured images">
<article class="card" tabindex="0">Slide A</article>
<article class="card" tabindex="0">Slide B</article>
<article class="card" tabindex="0">Slide C</article>
<article class="card" tabindex="0">Slide D</article>
</div>
Чому це працює:
grid-auto-flow: columnплюсgrid-auto-columnsдають «картки» з передбачуваною шириною без жорсткого задавання пікселів.overflow-x: autoстворює контейнер прокрутки. Немає контейнера — немає прив’язки.scroll-snap-type: x mandatoryзмушує прив’язуватись щоразу, коли прокрутка закінчується. Якщо хочете більш поблажливу поведінку, переключіться наproximity.tabindex="0"— простий спосіб зробити слайди фокусованими. Якщо в слайдах є посилання/кнопки, вони й без того будуть фокусовані — не додавайте tabindex зайвого, якщо не маєте на те причини.
Уникайте пастки «фейкової каруселі»: не ховайте смугу прокрутки, якщо у вас немає справжньої заміни. Люди використовують смуги прокрутки як індикатор стану, а не як декорацію.
Короткий жарт №1: Найшвидший спосіб «вирішити» scroll-snap — це видалити його. Другий за швидкістю — виміряти, що справді повільне.
Рішення дизайну, що роблять інтерфейс преміальним або зламаним
Mandatory vs proximity: виберіть свою битву
mandatory примусово застосовує прив’язку. Це чудово для досвіду «по одному картці за раз». Водночас цей режим дає відчуття ув’язнення, якщо точки прив’язки занадто часті або картки занадто вузькі. proximity поблажливіший: прив’язується лише коли зупинка досить близько.
Якщо користувачі ймовірно швидко пролистують стрічку, віддавайте перевагу proximity. Якщо галерея — це свідомий «степпер» (фото товару, онбординг), mandatory цілком доречний.
Вирівнювання snap: start vs center vs end
scroll-snap-align: start найпередбачуваніший, бо відповідає тому, як читають LTR-макети: контент починається з сталого краю. Центрований варіант виглядає «дизайнерським», але більш чутливий до округлень, padding контейнера та різних ширин вікна перегляду.
- Використовуйте
startколи картка має текст або будь-який ліво-вирівняний UI. - Використовуйте
centerколи картка — це переважно зображення і ви прагнете естетики «coverflow» (але без енергетики 2007 року). - Уникайте
endякщо тільки у вас немає специфічного правостороннього макета.
scroll-padding і scroll-margin: податок за липкий хедер
Якщо у вашому макеті є липкий хедер або оверлей, прив’язка може посадити контент під нього. Використовуйте scroll-padding на контейнері, щоб відсунути позиції прив’язки від країв. Використовуйте scroll-margin на дочірніх елементах, коли деякі елементи потребують інших відступів.
Типовий підхід:
- Контейнер має
scroll-padding-inline: 16px, щоб перша/остання картка не прилипали до краю вікна. - Контейнер має
scroll-padding-topякщо мова про вертикальну прив’язку (хоч це не наш фокус, принцип той самий).
Пробіл між елементами: gap vs margin, і чому це важливо
Точки прив’язки обчислюються від box-ів верстки. Якщо ви змішуєте gap, margins і псевдоелементи, у вас можуть вийти snap-позиції, які не збігаються з тим, що бачить дизайнер. Тримайте все простим:
- Використовуйте
gapу контейнері для відступів між елементами. - Уникайте margin на елементі, якщо не потрібна асиметрія.
- Якщо потрібен простір на початку/кінці, використовуйте padding контейнера плюс
scroll-padding.
Вкладена прокрутка: можливо, але буде коштувати
Найпоширеніша причина «чому здається зламаним» — вкладені контейнери прокрутки: горизонтальна галерея всередині вертикальної сторінки, плюс картка з власним overflow. Тоді трекпади і сенсорні пристрої мусять вгадувати, куди направити інтенцію прокрутки.
Використовуйте:
overscroll-behavior: containщоб зупинити ланцюжки прокрутки там, де це шкідливо.touch-action: pan-xна горизонтальному скролері, щоб зменшити вертикальну плутанину.
Поводження зі смугою прокрутки: стабільна верстка важливіша за красу
На деяких платформах смуги прокрутки накладні; на інших — займають місце. Коли смуга появляється/зникає, контент може зміститись, і точки прив’язки зрушать. Так з’являються скарги «запримітили, що воно прив’язалось не до того слайда», які важко відтворити.
Якщо можливо, використовуйте scrollbar-gutter: stable на контейнері прокрутки, щоб уникнути зсувів. Це не всюди підтримується, але там, де є, це простий виграш.
Не підганяйте під один пристрій
Мишине колесо відправляє дискретні дельти. Трекпад — високоточні безперервні дельти. Дотик — імпульс з інерцією. Scroll snapping працює поза цим усім. Тестування лише з мишкою — шлях до випуску галереї, що виглядає нормально вам і ворожо для інших.
Доступність і методи вводу: дотик, трекпад, клавіатура
Підтримка клавіатури без JS: фокус — ваш друг
Без JavaScript ви не будете «слухати клавіші стрілок» і програмно оновлювати позицію прокрутки. Але ви все ще можете забезпечити пристойний досвід для клавіатури:
- Робіть елементи фокусованими (
tabindex="0") або включайте фокусовані елементи всередині (посилання/кнопки). - Переконайтеся, що сфокусовані елементи видимі. Браузери зазвичай прокручують сфокусовані елементи в поле зору — часто з урахуванням snap.
- Використовуйте
:focus-visible, щоб показати чітке кільце фокусу.
Якщо ви хочете явні кнопки «далі/назад» без JS, можна робити їх за допомогою анкерів на ID слайдів. Це старомодно, але працює і доступно.
cr0x@server:~$ cat anchor-nav.html
<style>
.gallery { overflow-x: auto; scroll-snap-type: x mandatory; display: grid; grid-auto-flow: column; grid-auto-columns: 85%; gap: 16px; padding: 16px; }
.slide { scroll-snap-align: start; border: 1px solid #e6e6e6; border-radius: 12px; min-height: 200px; }
.nav a { margin-right: 10px; }
.gallery { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { .gallery { scroll-behavior: auto; } }
</style>
<div class="nav" aria-label="Gallery navigation">
<a href="#s1">1</a>
<a href="#s2">2</a>
<a href="#s3">3</a>
</div>
<div class="gallery">
<section id="s1" class="slide" tabindex="-1">Slide 1</section>
<section id="s2" class="slide" tabindex="-1">Slide 2</section>
<section id="s3" class="slide" tabindex="-1">Slide 3</section>
</div>
tabindex="-1" на слайдах дозволяє встановити фокус при навігації анкерами, не додаючи їх у порядок табуляції. Це зберігає нормальний порядок табуляції на сторінці.
Reduced motion — це не опція
Якщо ви додаєте scroll-behavior: smooth, ви мусите поважати prefers-reduced-motion. Плавна прокрутка може викликати нудоту в деяких користувачів і ускладнює відладку, бо кожна взаємодія стає анімацією. Робіть плавну прокрутку умовною і рухайтесь далі.
Екранні читачі та семантика
Scroll-snap галереї все ще — просто контейнери прокрутки. Не намагайтеся видавати їх за «віджет каруселі», якщо ви не реалізуєте повну інтерактивну семантику (що зазвичай означає JS). Дотримуйтеся коректного HTML:
- Використовуйте
<section>,<article>,<figure>з<figcaption>і змістовні заголовки. - Позначте регіон
aria-label, якщо він не описаний сусіднім текстом. - Не замикати фокус всередині галереї.
touch-action: різкий інструмент
touch-action: pan-x може покращити горизонтальну інтенцію на сенсорних пристроях, але будьте обережні: якщо галерея знаходиться всередині вертикальної сторінки, користувачі повинні мати можливість вертикально прокручувати, коли палець не ідеально горизонтальний. Тестуйте. Якщо галерея висока й насичена контентом, можливо варто лишити touch-action як є і покладатися на належні відступи та overscroll-behavior.
Продуктивність: справжнє вузьке місце рідко — «scroll snap»
Коли хтось каже «scroll snap підвисає», зазвичай мають на увазі «прокрутка підвисає, коли прив’язка увімкнена». Це важлива різниця. Прив’язка може проявити проблеми продуктивності, що вже були: перевантажені зображення, трясіння верстки, важкі перефарбування та вкладені композиції.
Що робить прив’язку неприємною
- Зсуви макета під час прокрутки: зображення без розмірів, підвантаження шрифтів, динамічний контент, що змінює розмір карток.
- Важке малювання: великі box-shadow, фільтри, backdrop-filter та великі прозорі шари.
- Навантага на основний потік: ефекти, прив’язані до скролу, дорогі CSS-селектори, занадто багато липких елементів.
- Тиск на пам’ять: багато високодетальних зображень, декодування яких одночасно змушує браузер звільняти поверхні.
Спочатку стабілізуйте верстку: зафіксуйте співвідношення сторін зображень
Якщо висота картки змінюється під час прокрутки, геометрія контейнера змінюється і точки прив’язки можуть «рухатись». Виправлення нудне, але ефективне: задайте width/height атрибути зображень (або aspect-ratio в CSS), щоб браузер зарезервував місце перед декодуванням.
Containment і content-visibility: використовуйте, але перевіряйте
content-visibility: auto і contain можуть покращити продуктивність для великих сторінок, пропускаючи відмалювання поза екраном. Але в галереях з snap «поза екраном» часто означає лише одну картку поруч — і прив’язка може змусити миттєво відкривати її. Надмірне використання може створити ефект блимання при посадці snap.
Використовуйте їх, коли у вас багато важких слайдів. Потім перевіряйте на слабких пристроях і в Safari. Якщо бачите бланкінг, зменшіть агресивність або підготуйте один слайд наперед.
Композиція: не створюйте випадково 40 шарів
Пасажі типу will-change: transform часто використовують як універсальний хак — це еквівалент маркування кожного листа як «ТЕРМІНОВО». Так ви спалюєте пам’ять і отримуєте дивні глюки.
Короткий жарт №2: will-change — це як писати «ТЕРМІНОВО» на кожному листі — врешті-решт нічого не буде терміновим.
CSS scroll snapping і плавна прокрутка
scroll-behavior: smooth може маскувати проблеми, роблячи рух «запроектованим», але також підсилює відчуття затримки, бо анімації будуть ривкими під навантаженням. Не використовуйте плавну прокрутку як лейкопластир продуктивності. Виправляйте корінь проблем: верстку й малювання.
Факти та коротка історія, які варто знати
- Ідеї «snap points» існували в UI-інструментарях задовго до CSS — наприклад, пагінація в нативних мобільних фреймворках.
- CSS Scroll Snap виник як ініціатива W3C, щоб формалізувати поведінку «пагінації» для сенсорно-орієнтованих інтерфейсів, особливо з ростом мобільного браузингу.
- Назви властивостей змінювалися з часом; старі чернетки використовували інші імена, тому в мережі ще можна знайти застарілі приклади.
scroll-snap-stopз’явився тому, що користувачі скаржилися на пропуск елементів при швидкому змахуванні; це регулятор для «обов’язково зупинитися тут».- Інерційна прокрутка сама по собі — не CSS-фіча, а поведінка платформи. Алгоритми snap повинні співіснувати з фізикою ОС.
- Рендеринг прокрутки в рушіях браузера — високопріоритетний пайплайн; сучасні реалізації намагаються тримати прокрутку поза основним потоком, коли можливо.
- Scroll snapping взаємодіє з функціями доступності, як-от reduced motion; користувачі можуть перебити ваші наміри, і так має бути.
- RTL (right-to-left) ускладнює горизонтальну прив’язку, бо «start» і «end» міняються місцями; хороше тестування включає RTL, навіть якщо продукт переважно англомовний.
- Рендеринг смуг прокрутки відрізняється в різних ОС і налаштуваннях; snap-позиції, що припускають певний gutter, можуть зміщуватися, коли смуги не накладні.
Три корпоративні міні-історії з передової
Інцидент: неправильне припущення («snap points — це просто краї картки»)
У середній компанії продуктова команда випустила scroll-snap галерею для сторінки з цінами. На скріншотах у десктопному Chrome все виглядало чудово. На iOS Safari користувачі повідомляли, що галерея «відмовляється зупинятися» і інколи прив’язується до напівкартки.
Неправильне припущення було підступне: інженер думав, що точки прив’язки вирівняються по візуальному лівому краю картки. Але дизайн використовував негативні відступи (negative margins) для ефекту «перехльосту» і псевдоелемент для градієнтного затемнення. Box-геометрія не збігалася з візуальним краєм.
Саппорт швидко відтворив проблему на iPhone. Інженери не могли відтворити її на MacBook з трекпадом. Це спричинило затримку з виправленням, бо всі сперечалися, чи це «реально». Це було реально. Воно залежало від пристрою вводу і від box-геометрії.
Виправлення було нудним: прибрали негативні margin, перемістили декоративний перехльост в padding та фонові шари всередині картки, і використали container gap. Точки прив’язки стали стабільними, бо вони відповідали box-ам, які браузер використовує для обчислення snap.
Висновок: коли ви будуєте scroll snap, ваші box-геометрії — це API. Якщо візуал не збігається з box-ами, браузер прив’яже до box-ів, а не до ваших намірів.
Оптимізація, що відбилася боком («ми будемо lazy-render всіх»)
Інша команда мала скролер «фіч-тилів» на головній. Продуктивність на ноутбуках була ок. На Android-пристроях спостерігався сильний стуттер. Інженер додав content-visibility: auto на кожну картку і агресивно відкладав завантаження зображень, щоб зменшити початкову роботу.
Метрики покращилися для початкового рендеру. Всі святкували. Потім почалися багрепорти: користувачі змахували галерею і бачили порожні плитки на мить, інколи довго, достатньо, щоб виглядало зламано. Snap робив це гіршим, бо він змушував перегляд «приземлятись» на плитку, яка ще не була відрендерена.
Корінь проблеми не в тому, що lazy rendering поганий. Проблема в тому, що scroll-snap робить «наступний елемент» гарантованою ціллю в найближчому часі. Якщо ви занадто агресивно відтягуєте рендеринг, ви створюєте помітну діру саме у момент взаємодії — користувачі сприймають це як лаг.
План відновлення: залишити content-visibility, але обмежити його: рендерити поточну плитку плюс один вперед/назад, забезпечивши, що ці елементи «достатньо видимі» (наприклад, менш агресивний поріг або не застосовувати до найближчих сусідів). Також задекларували розміри зображень і використовували responsive джерела, щоб зменшити піки декодування.
Урок: оптимізуйте для взаємодії, а не для скріншотів у Lighthouse. Галерея — це інтерфейс взаємодії. Ставте її як «гарячий» шлях, бо так воно і є.
Нудна, але правильна практика, що врятувала ситуацію («фіч-флаг + швидке відкотування»)
Ритейлер замінив JS карусель на CSS scroll snap, щоб зменшити розмір бандлу. Зміна була за фіч-флагом. Без пафосу. Просто поступове розгортання з можливістю відключити.
Під час нарощування трафіку customer success повідомив, що деякі користувачі на старих версіях Safari бачили дивну «резинову» поведінку: галерея перебільшено відскочувала, потім прив’язувалась не до того елемента. Це не було універсально, і для відтворення потрібні були специфічні налаштування ОС.
Замість того, щоб витрачати вихідні на суперечки про те, «хто винен», on-call SRE зробив те, що має робити: зменшив площу ураження. Вони вимкнули прапор для проблемних UA під час розслідування. Ніякої драми. Ніяких екстрених патчів. Ніяких нічних деплоїв.
Інженери потім зробили дрібну UA-мітігацію: для проблемних версій Safari вони переключили mandatory на proximity і прибрали плавну прокрутку. Також додали overscroll-behavior і спростили вкладені переповнення. Рівень багрепортів впав до фонових шумів.
Практика, що врятувала ситуацію, була не в хитромудрому CSS. Це була операційна дисципліна: поступове розгортання, вимірювані звіти про помилки та шлях відкату, що не вимагав героїзму.
Швидкий план діагностики
У вас є scroll-snap галерея. Користувачі кажуть, що вона підвисає, стрибає до неправильних елементів або взагалі не прив’язується. Вам потрібен швидкий шлях до вузького місця без перетворення дебагу на стиль життя.
Перше: підтвердіть, що контейнер і точки прив’язки реальні
- Перевірте, чи контейнер прокрутки — це елемент, який ви думаєте (
overflow-x: autoна правильному вузлі). - Переконайтеся, що діти є прямими учасниками з
scroll-snap-align(а не застосовано до внутрішньої обгортки, до якої фактично не виконується прокрутка). - Перевірте на вкладені контейнери overflow всередині слайдів, які можуть красти прокрутку або викликати ланцюжок прокрутки.
Друге: ізолюйте зсуви верстки
- Вимкніть завантаження зображень (або замініть на заповнювачі з фіксованими розмірами) і подивіться, чи зникає проблема.
- Перевірте, чи шрифти не підвантажуються після першого рендеру.
- Шукайте динамічне додавання контенту (реклама, персоналізація, модулі «рекомендовано»), що змінює розмір картки.
Третє: профілюйте продуктивність під час прокрутки як дорослий
- Запишіть профіль продуктивності під час прокрутки і посадки snap.
- Шукайте довгі таски на основному потоці під час скролу.
- Шукайте важке малювання і композицію (великі тіні, фільтри, елементи з фіксованою позицією).
Четверте: тестуйте через методи вводу і налаштування
- Трекпад vs колесо миші vs дотик (реальний пристрій, якщо можливо).
- Reduced motion увімкнено.
- RTL, якщо ваш продукт це підтримує (або може підтримувати).
Правило рішення: якщо snap неправильний — виправляйте геометрію і snap-налаштування. Якщо прив’язка правильна, але взаємодія неприємна — виправляйте продуктивність і обробку вводу (overscroll/touch-action), або пом’якшуйте mandatory до proximity.
Практичні завдання: команди, виводи та рішення
Ось перевірки, які я очікую в реальному інцидент-каналі: швидкі, детерміновані й прив’язані до рішення. Команди можна виконати на типовій Linux-розробницькій машині. Коли перевірка фронтендова, ми все одно використовуємо системні інструменти, бо дебаг продакшн — крос-дисциплінарний.
Завдання 1: Підтвердити, що CSS галереї дійсно задеплоєний
cr0x@server:~$ curl -sS -D- https://example.test/gallery | head
HTTP/2 200
content-type: text/html; charset=utf-8
cache-control: public, max-age=60
etag: "a1b2c3"
...
Що означає вивід: Отримуємо 200 з HTML. Базова мережна доступність поряд.
Рішення: Якщо бачите редирект-луп або 500 — перестаньте звинувачувати CSS і спочатку виправте доставку.
Завдання 2: Перевірити, що CSS містить правила scroll-snap
cr0x@server:~$ curl -sS https://example.test/assets/app.css | grep -n "scroll-snap" | head
1842:.gallery{scroll-snap-type:x mandatory;scroll-padding-inline:16px}
1843:.card{scroll-snap-align:start}
Що означає вивід: CSS-бандл містить властивості. Якщо grep нічого не повертає — ваш pipeline міг tree-shake стилі або ви отримуєте неправильний asset.
Рішення: Відсутні правила — це проблема деплою/будду, а не баг браузера. Виправте бандл або селектори.
Завдання 3: Перевірити, чи працює стиснення (великий CSS/HTML може затримувати взаємодію)
cr0x@server:~$ curl -sS -I https://example.test/assets/app.css | egrep -i "content-encoding|content-length|cache-control"
cache-control: public, max-age=31536000, immutable
content-encoding: br
content-length: 41231
Що означає вивід: Brotli увімкнено, розмір керований, кешування сильне.
Рішення: Якщо стиснення відсутнє і content-length гігантський — виправте це перед мікрооптимізаціями поведінки скролу.
Завдання 4: Виміряти розмір зображень (занадто великі зображення — головна причина jank)
cr0x@server:~$ curl -sS -I https://example.test/media/slide-1.jpg | egrep -i "content-type|content-length|cache-control"
content-type: image/jpeg
content-length: 8421932
cache-control: public, max-age=31536000
Що означає вивід: Це близько 8 МБ для одного зображення. На мобільних це проблема навіть при ідеальному CSS.
Рішення: Додайте responsive images, сучасні формати і задайте розміри. Зменшіть байти перш ніж сперечатись про вирівнювання snap.
Завдання 5: Підтвердити підтримку range-запитів на сервері (допомагає з медіа)
cr0x@server:~$ curl -sS -I https://example.test/media/slide-1.jpg | egrep -i "accept-ranges"
accept-ranges: bytes
Що означає вивід: Range-запити дозволені.
Рішення: Якщо відсутні — велике медіа доставляється менш ефективно. Перевірте налаштування CDN/origin.
Завдання 6: Виявити довгі таски під час прокрутки на тестовій машині (системний CPU-тиск)
cr0x@server:~$ top -b -n 1 | head -n 12
top - 10:21:14 up 12 days, 4:02, 1 user, load average: 2.11, 1.88, 1.74
Tasks: 238 total, 1 running, 237 sleeping, 0 stopped, 0 zombie
%Cpu(s): 18.2 us, 3.3 sy, 0.0 ni, 77.8 id, 0.0 wa, 0.0 hi, 0.7 si, 0.0 st
MiB Mem : 15948.5 total, 2142.9 free, 6120.3 used, 7685.3 buff/cache
MiB Swap: 2048.0 total, 2048.0 free, 0.0 used. 9182.2 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4121 cr0x 20 0 3241560 482912 156324 S 48.0 3.0 10:12.33 chrome
Що означає вивід: Chrome споживає багато CPU під час тесту взаємодії.
Рішення: Якщо CPU під завантаженням під час скролу — чекайте підвисань. Переходьте до профілювання браузера і зменшення роботи з paint/layout.
Завдання 7: Перевірити, чи система свопить (своп робить усе наче зачарованим)
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 15Gi 5.9Gi 2.1Gi 268Mi 7.5Gi 8.9Gi
Swap: 2.0Gi 0B 2.0Gi
Що означає вивід: Своп не використовується. Добрий базовий стан для надійного тестування продуктивності.
Рішення: Якщо своп використовується інтенсивно, ваш тест «scroll snap jank» недійсний. Усуньте проблему стану машини спочатку.
Завдання 8: Виявити сигнали зсуву макета в логах польових даних (кореляція CLS)
cr0x@server:~$ journalctl -u webapp -n 30 | grep -i "CLS" | tail
Dec 29 10:19:12 web01 webapp[2381]: rum metric CLS=0.21 route=/pricing device=mobile
Dec 29 10:19:48 web01 webapp[2381]: rum metric CLS=0.19 route=/pricing device=mobile
Що означає вивід: CLS у полі підвищений на сторінці з галереєю.
Рішення: Пріоритезуйте стабільність верстки (розміри зображень, завантаження шрифтів) перед налаштуванням snap. Snap не виправить рухому геометрію.
Завдання 9: Перевірити, чи шрифти не великі або повільні (своп шрифтів може змінювати ширини карток)
cr0x@server:~$ curl -sS -I https://example.test/assets/fonts/Inter-var.woff2 | egrep -i "content-length|cache-control|content-type"
content-type: font/woff2
content-length: 986432
cache-control: public, max-age=31536000, immutable
Що означає вивід: Майже 1 МБ шрифт. Не обов’язково погано, але це підозрілий фактор для пізніх перескакувань.
Рішення: Розгляньте субсетинг або стратегію font-display, що не викликає видимих зсувів у картках.
Завдання 10: Перевірити коректність HTTP-кешування (щоб не перезавантажувати ресурси галереї)
cr0x@server:~$ curl -sS -I https://example.test/assets/app.css | egrep -i "etag|last-modified|cache-control"
cache-control: public, max-age=31536000, immutable
etag: "d34db33f"
Що означає вивід: Сильне кешування з immutable-ресурсами.
Рішення: Якщо кешування слабке — користувачі заново завантажують ресурси і взаємодія починається пізно. Виправте кешування перед суперечками про деталі CSS.
Завдання 11: Підтвердити, що сторінка випадково не відключає overflow прокрутки
cr0x@server:~$ rg -n "overflow-x:\s*hidden|overflow:\s*hidden" -S ./src | head
src/styles/layout.css:44:body { overflow-x: hidden; }
src/components/Gallery.css:3:.gallery { overflow-x: auto; }
Що означає вивід: body має overflow-x: hidden. Це може бути нормальним, але часто причиною багів «не можна прокрутити галерею» у поєднанні з іншими обмеженнями верстки.
Рішення: Якщо галерея не прокручується на деяких пристроях, аудитуйте глобальні правила overflow і розміри контейнерів.
Завдання 12: Перевірити випадкове зміщення розмірів контейнера прокрутки (в’юпортні одиниці, динамічні тулбари)
cr0x@server:~$ rg -n "100vw|100vh|dvh|svh|lvh" ./src/styles | head
src/styles/gallery.css:12:.gallery { width: 100vw; }
src/styles/page.css:8:.page { min-height: 100vh; }
Що означає вивід: width: 100vw може включати ширину смуги прокрутки на деяких платформах, викликаючи тонкий горизонтальний overflow і дрейф snap.
Рішення: Віддавайте перевагу width: 100% для контейнерів і керуйте padding явно. Якщо потрібне розміщення по вьюпорту, тестуйте поведінку смуги прокрутки і мобільних динамічних тулбарів.
Завдання 13: Перевірити, чи не ввели ви великі витрати на paint (box-shadow скрізь)
cr0x@server:~$ rg -n "box-shadow:|filter:|backdrop-filter:" ./src/styles | head -n 12
src/styles/cards.css:18:.card { box-shadow: 0 24px 80px rgba(0,0,0,0.22); }
src/styles/hero.css:9:.hero { backdrop-filter: blur(18px); }
Що означає вивід: Великі м’які тіні і backdrop filters — важка робота для paint, особливо під час прокрутки.
Рішення: Зменшіть розмиття/розмах тіней, відмовтеся від backdrop-filter у контекстах прокрутки або обмежте ефекти до непрацюючих шарів.
Завдання 14: Підтвердити, що збірка не видалила префіксні або fallback-властивості (особливості Safari)
cr0x@server:~$ node -p "process.versions.node"
22.11.0
Що означає вивід: У вас сучасне Node-середовище. Якщо ваш CSS-пайплайн теж сучасний, він може покладатися на фічі, які цільові браузери не підтримують без фолбеків.
Рішення: Перевіряйте CSS відповідно до матриці підтримуваних браузерів. Якщо Safari входить до підтримки — валідуйте поведінку на реальному Safari, а не лише на «WebKit-подібних» рішеннях.
Завдання 15: Аудит кількості елементів галереї (занадто багато слайдів = тиск пам’яті)
cr0x@server:~$ python3 - <<'PY'
from bs4 import BeautifulSoup
html = open("gallery.html","r",encoding="utf-8").read()
s = BeautifulSoup(html,"html.parser")
print("cards:", len(s.select(".card")))
PY
cards: 4
Що означає вивід: Приклад малий. На реальних сторінках часто 30+ елементів.
Рішення: Якщо у вас багато слайдів з важким контентом, розгляньте пагінацію, зменшення числа елементів або іншу стратегію рендерингу — але тестуйте на бланкінг з snap.
Поширені помилки: симптоми → корінь проблеми → виправлення
1) «Воно взагалі не прив’язується»
- Симптоми: Горизонтальна прокрутка працює, але елементи ніколи не вирівнюються чітко.
- Корінь проблеми:
scroll-snap-typeне на справжньому контейнері прокрутки, або контейнер не прокручується (немає overflow). - Виправлення: Помістіть
overflow-x: autoіscroll-snap-type: x ...на той самий елемент. Переконайтесь, що його ширина обмежена, щоб був overflow.
2) «Воно прив’язується до дивних напівпозицій»
- Симптоми: Елементи вирівнюються непослідовно; інколи видно половину наступної картки.
- Корінь проблеми: Візуальний вигляд не збігається з box-геометрією (негативні margin, трансформи, псевдоелементи, що впливають на сприйняті краї).
- Виправлення: Приберіть негативні margin з snap-елементів; використовуйте
gapта padding. Тримайте box-і елементів вирівняними з тим, що бачить користувач.
3) «Прокрутка сторінки застрягає в галереї»
- Симптоми: На мобільних вертикальна прокрутка стає важкою, коли палець проходить над галереєю.
- Корінь проблеми: Горизонтальний контейнер захоплює інтенцію дотику; вкладений ланцюжок прокрутки; агресивний
touch-action. - Виправлення: Використовуйте
overscroll-behavior-x: containі розгляньте зняття або пом’якшенняtouch-action. Зробіть галерею нижчою, щоб вона менш імовірно перехоплювала вертикальну прокрутку.
4) «Прив’язка приземляється під хедером»
- Симптоми: Початок картки ховається під липким UI.
- Корінь проблеми: Немає
scroll-paddingабоscroll-marginдля врахування оверлеїв. - Виправлення: Встановіть
scroll-padding-inlineабоscroll-padding-topна контейнері залежно від осі.
5) «На десктопі гладко, на телефонах жахливо»
- Симптоми: Мобільні підвисання, бланкінг, затримка появи зображень при snap.
- Корінь проблеми: Занадто важкі зображення і час декодування; надто агресивне lazy-rendering; важкі ефекти малювання.
- Виправлення: Використовуйте responsive images, задайте розміри/aspect-ratio, спростіть тіні/фільтри та уникайте приховування рендерингу для сусідів поза екраном.
6) «Воно стрибає, коли з’являється смуга прокрутки»
- Симптоми: На десктопі галерея трохи зсувається і потім snap-вирівнювання здається неправильним.
- Корінь проблеми: Гаттер смуги прокрутки змінює макет, часто через налаштування ОС або hover-scrollbars.
- Виправлення: Використовуйте
scrollbar-gutter: stableде підтримується; інакше переконайтесь, що розміри контейнера не залежать від100vw.
7) «Safari ігнорує мою гарну поведінку»
- Симптоми: Інше settle-поведінка, ніж у Chromium/Firefox; дивні резинові ефекти.
- Корінь проблеми: Різниці рушіїв і фізики інерції; також часто вкладені overflow і трансформи.
- Виправлення: Спроостіть: менше вкладених зон прокрутки, уникайте трансформів на батьках контейнера скролу, розгляньте перехід на
proximity, приберіть плавну прокрутку для проблемних збірок.
8) «Користувачі клавіатури не можуть дістатися контенту»
- Симптоми: Tab не заходить у слайди, або кільце фокусу зникає за межами екрану.
- Корінь проблеми: Нема фокусованих елементів; стилі фокусу видалені; обрізка overflow без поведінки scroll-to-focus.
- Виправлення: Переконайтесь, що є фокусовані елементи; додайте стилі
:focus-visible; не використовуйтеoutline: noneбез адекватної заміни.
Контрольні списки / покроковий план
Покроково: побудуйте snap-галерею, яка не буде вас компрометувати пізніше
- Виберіть модель верстки. Використовуйте CSS Grid columns для карток. Уникайте хитрих float-ів, негативних margin і трансформів на контейнері прокрутки.
- Створіть реальний контейнер прокрутки.
overflow-x: auto, ширина обмежена батьком, ніяких глобальних правил overflow, що йому заважають. - Визначте поведінку snap. Почніть з
scroll-snap-type: x proximity, якщо немає крайньої потреби в mandatory. - Встановіть вирівнювання елементів. За замовчуванням —
scroll-snap-align: start. - Додайте передбачувані відступи. Використовуйте
gapі padding контейнера; встановітьscroll-padding-inlineвідповідно. - Зробіть це зручним для клавіатури. Забезпечте наявність фокусованих елементів; додайте
:focus-visible. - Поважайте reduced motion. Увімкніть плавність тільки коли дозволено налаштуваннями руху.
- Стабілізуйте медіа-верстку. Оголосіть розміри або aspect-ratio зображень; уникайте пізнього завантаження контенту, що змінює розмір картки.
- Глядіть на вкладені переповнення. Не вміщуйте прокручувані області всередині прокручуваних слайдів, якщо це не критично.
- Тестуйте різні методи вводу. Миша, трекпад, дотик і принаймні один Safari.
- Випускайте за флагом, якщо ризикований реліз. Особливо для маркетингових сторінок з високим трафіком, де «маленькі UX-регресії» дорівнюють реальним грошам.
- Вимірюйте й ітеруйте. Моніторьте CLS, latency взаємодії та відгуки користувачів по кореляції з пристроями/браузерами.
Передпусковий чеклист (швидко)
Питання та відповіді
1) Користуватися mandatory чи proximity?
За замовчуванням обирайте proximity, якщо галерея не є явно кроковою (фото продукту, онбординг). mandatory збільшує скарги про «застрягнення», якщо точки прив’язки часті або картки вузькі.
2) Чому на трекпаді поведінка відрізняється від мишиного колеса?
Бо потік вводу різний: трекпади дають безперервні високоточні дельти; колеса миші — грубі такти. Snap відбувається після стабілізації прокрутки, а таймінг «стабілізації» різниться по пристроях.
3) Чи можна побудувати повноцінну карусель (дотс, next/prev, autoplay) без JS?
Навігацію можна зробити за допомогою анкерів і красиво стилізувати. Autoplay без JS — погана ідея з погляду доступності і контролю користувача. Для «дотс» використовуйте посилання на ID слайдів і тримайте це простим.
4) Чи робить scroll-behavior: smooth дотикову прокрутку плавною?
Не зовсім. Воно в основному впливає на програмну прокрутку і деякі дії, що викликає UA. Інерція дотику — це фізика платформи. Не покладайтеся на smooth для виправлення jank.
5) У моїх слайдів є padding — чому snap зміщений на кілька пікселів?
Зазвичай це через округлення і box-sizing. Віддавайте перевагу вирівнюванню до стабільного краю (start), зіставляйте padding контейнера з scroll-padding і уникайте фракційних ширин коли можливо (наприклад, 33.333% плюс великі gap).
6) Як зупинити прокрутку сторінки під час взаємодії з галереєю?
Використовуйте overscroll-behavior-x: contain на галереї. Розгляньте touch-action: pan-x обережно; це може покращити інтенцію, але й ускладнити вертикальну прокрутку поверх галереї.
7) Чому деякі елементи «перескакують», коли я швидко змахую?
Інерція може віднести прокрутку за кілька snap-точок. Якщо вам потрібно жорстко зупинятись на кожному елементі, спробуйте scroll-snap-stop: always на елементах — але протестуйте, це може відчуватись обмежуюче.
8) Чи ховати смугу прокрутки заради чистого вигляду?
Тільки якщо ви замінюєте її на щось настільки ж явне і доступне. Інакше ви вилучаєте підказку користувача і покладаєтеся на «настрій». Якщо й ховаєте — переконайтеся, що клавіатурна і дотикова взаємодія відмінні.
9) Чи може scroll snap спричинити CLS?
Сам по собі scroll snap не створює CLS. Але він робить зсуви макета більш помітними, бо користувач очікує стабільної посадки. CLS зазвичай походить від зображень без зарезервованого простору, пізніх підвантажень шрифтів або динамічної ін’єкції контенту.
10) Який найпростіший спосіб зробити це адаптивним?
Використовуйте grid auto-columns у відсотках і підлаштовуйте на брейкпоінтах. Приклад: 85% на малих екранах (одна картка здебільшого видима), 45% на широких (приблизно дві картки видимі). Потім підганяйте gap і scroll-padding.
Висновок: що робити далі
Якщо ви хочете плавні горизонтальні галереї без JavaScript, scroll snap — правильний інструмент. Але це не магія. Ставтеся до нього як до будь-якої production-фічі: тримайте геометрію чесною, верстку стабільною і вимірюйте вузькі місця замість здогадок.
Кроки на цей тиждень:
- Переробіть галерею в один очевидний контейнер прокрутки з передбачуваними box-ами дочірніх елементів (grid + gap + padding).
- Переключіться на
proximity, якщо продукт справді не потребує суворої прив’язки, і додайтеscroll-padding-inlineвідповідно до дизайну. - Виправте медіа: задайте розміри/aspect-ratio і зменшіть байти зображень, поки мобільні пристрої не перестануть перегріватися.
- Прогрійте швидку діагностику на Safari і на одному реальному телефоні перед тим, як сперечатись про «чудеса браузерів».
- Якщо сторінка критична, випускайте за флагом і будьте готові до відкату. Це не песимізм — це як зберегти вихідні дні.