Скролінг в майбутнє

17 хв. читання

Можливість прогортати інформацію сприймається як належне всіма користувачами. Однак реалізувати скролінг, який добре працюватиме поміж браузерами й чудово вписуватиметься в дизайн, може бути непросто. Коли веб-стандарти розвиваються швидше, ніж коли-небудь, практики програмування часто відстають.

Ознайомтеся з деякими загальними випадками реалізації скролінгу й перевірте, чи не було рішення, яке ви використовуєте, замінене чимсь більш елегантним.

Цікавий випадок зникнення скролбару

За останні 30 років зовнішній вигляд скролбару постійно змінювався відповідно до тенденцій дизайну. Кольори, тіні, форми стрілок, радіус країв — дизайнери інтерфейсів експериментували зі всім. Ось як ця подорож виглядала у Windows:

Скролінг в майбутнє
Скролбари у Windows

У 2011 дизайнери інтерфейсів Apple, перейнявши ідею iOS, остаточно припинили всі спроби «прикрасити» скролбар. Всюдисущий елемент дизайну просто зник зі всіх Mac. Він більше не займає ніякого місця в статичному представлені, і з'являється тільки тоді, коли користувач починає скролити.

Скролінг в майбутнє
Скролбари на Mac

Тиха смерть скролбару ніколи не оплакувалась користувачами техніки Apple. Вони, звиклі до реалізації скролінгу на iPhone та iPad, швидко прийняли ці зміни.


Однак, ми все ще живемо в світі з багатьма операційними системами й реалізаціями браузерів. Якщо ви веб-розробник, то ви не можете просто відкинути «питання реалізації скролбару».


В цій статті показуються деякі трюки, які можуть зробити досвід прогортання сторінок більш приємним для ваших користувачів.

Сховати й прокрутити

Візьмемо класичний приклад модального вікна. Коли воно відкривається, основний контент сторінки не повинен прогортатись. Ось швидкий спосіб зробити це в CSS:

body {
  overflow: hidden;
}

Але цей код призводить до неприємного ефекту, зображеного нижче:

Скролінг в майбутнє
Дивіться на червону стрілку

У цьому прикладі в цілях демонстрації скролбар на Mac був змушений залишатися видимим в системних налаштуваннях (і ще для того, щоб ми могли відчути досвід користувачів Windows).

Як ми вирішимо цю проблему? Якщо відома ширина скролбару, то можна додавати зсув праворуч від головної сторінки кожного разу, коли відкривається модальне вікно.

Але ширину нелегко вгадати, оскільки різні операційні системи й браузери встановлюють її по-різному:

Скролінг в майбутнє
Піксель тут, піксель там...

Зверніть увагу на те, що ці розміри стосуються тільки поточних версій браузерів під Windows. В минулих версіях ситуація була іншою, і ніхто не знав, як це зміниться в майбутньому.

Замість вгадування, ви можете динамічно розраховувати ширину скролбару, звертаючись до JavaScript:

const outer = document.createElement('div');
const inner = document.createElement('div');
outer.style.overflow = 'scroll';
document.body.appendChild(outer);
outer.appendChild(inner);
const scrollbarWidth = outer.offsetWidth - inner.offsetWidth;
document.body.removeChild(outer);

Це всього лише сім рядків коду, але вони взаємодіють з DOM. Краще уникати маніпуляцій з DOM, тільки якщо це не життєво необхідно.

Інший спосіб вирішити проблему — залишити скролбар видимим, навіть за модальним вікном. Ось як це робиться на чистому CSS:

html {
  overflow-y: scroll;
}

«Стрибкова модальна проблема (jumpy modal problem)» вирішена, але ви залишились з недієспособним скролбаром, який може стати візуальною болячкою вашого дизайну.

На нашу думку, найкращим рішенням було б видалення скролбару в цілому. Це також можна зробити за допомогою стилів. Цей підхід не копіює в точності поведінку macOS, бо скролбар не з'являється під час скролінгу: він завжди залишається прихованим, але на сторінці залишається можливість прокручування. Для Chrome, Safari та Opera можна використовувати цей CSS:

.container::-webkit-scrollbar {
  display: none;
}

Для Internet Explorer та Edge:

.container {
  -ms-overflow-style: none;
}

А ось з Firefox, на жаль, вам не пощастило: в ньому нема ніякого способу позбавитися від скролбару.

Як ви бачите, немає ніякого магічного рішення цієї проблеми. Кожний описаний нами підхід має свої переваги й недоліки. Оберіть той, який найкраще підходить вашому проекту.

Боротьба зовнішніх виглядів

Признаймо це: стандартна реалізація скролбару в деяких операційних системах виглядає не зовсім гарно. Дизайнери вважають за краще мати повний контроль над зовнішнім виглядом свого застосунку, нічого не залишаючи в стороні. Існує сотні репозиторіїв GitHub з користувацькими реалізаціями скролбарів на JavaScript, які повністю замінюють стандартні системні.


Але що, якщо ви хочете кастомізувати наявний скролбар? Немає ніякого загального API, кожен вендор дозволить вам робити різні речі.


Internet Explorer дозволяє вам працювати над виглядом скролбару з версії 5.5, але можна міняти тільки кольори. Ось як перефарбувати бігунок (частина, яку ви перетягуєте) та стрілки:

body {
  scrollbar-face-color: blue;
}

Але гратися с кольорами ледве достатньо для повного користувацького досвіду. Розробники WebKit зрозуміли це й запропонували свій власний спосіб стилізації вже в 2009 році. Ось як ви можете використовувати вендорний префікс -webkit для відтворення скролбару macOS у будь-якому браузері WebKit:

::-webkit-scrollbar {
  width: 8px;
}
::-webkit-scrollbar-thumb {
  background-color: #c1c1c1;
  border-radius: 4px;
}

Ці налаштування підтримуються на десктопних Chrome, Safari, Opera, і навіть на смартфонах в UC Browser або Samsung Internet. Edge також планував це реалізувати, але три року потому ця ідея досі знаходиться в беклозі з середнім пріоритетом.

Коли діло доходить до кастомизації скролбару, Mozilla Foundation є абсолютним чемпіоном в ігноруванні потреб дизайну. Запит на створення можливості робити скролбари у Firefox стилізованими був відкритий 17 років тому, на початку тисячоліття. І тільки кілька місяців тому Джефф Гріффітс (менеджер продуктів для розробників у Firefox) нарешті підтримав тред, давши відповідь:

У мене немає ніякого інтересу щось робити, поки не з'явиться актуальна специфікація, хіба що хтось з команд платформи не зацікавлений у прототипуванні специфікації.

Заради справедливості, з точки зору W3C, підхід WebKit, хоча й добре підтримуваний, офіційно не існує. Наявний проект для специфікації CSS Scrollbars повторює IE: кастомізація обмежується зміною кольорів.

Битва продовжується. З відкритими питаннями, в яких благають про підтримку кастомізацій WebKit. Якщо хочете спробувати вплинути на робочу групу CSS — настав час приєднатися до дискусії. Можливо, ця проблема не є головним пріоритетом, але стандартизована підтримка могла б полегшити життя багатьом фронтенд розробникам й дизайнерам.

Оператор плавності

Найбільш розповсюдженою задачею, що включає скролінг, є навігація по цільовій сторінці. Зазвичай це робиться за допомогою анкор посилання. Все, що вам потрібно знати — id елемента:

<a href="#section">Section</a>

Клацання на цьому посиланні призводить до переходу до розділу, і часто UX дизайнери наполягають на додаванні анімації, щоб зробити скролінг плавним. На GitHub існує безліч готових рішень, які використовують JavaScript, але в наші дні те саме можна досягти лише з допомогою одного рядка коду. З недавніх часів Element.scrollIntoView() з DOM API приймає options object з ключем behavior, що забезпечує плавний скролінг «із коробки».

elem.scrollIntoView({
  behavior: 'smooth'
});

Підтримка параметрів браузером все ще доволі обмежена й залишається лише скриптом. Слід уникати додаткових скриптів, де це можливо.

На щастя, є нова властивість CSS (все ще в стані розробки), яка може змінити поведінку скролінгу всієї сторінки за допомогою одного рядка коду:

html {
  scroll-behavior: smooth;
}

Ось результат:

Скролінг в майбутнє
Стрибання з одного розділу на другий
Скролінг в майбутнє
Плавний скролінг

Ви можете погратися з цією властивістю у цьому codepen. На момент написання цієї статі, scroll-behavior підтримується тільки в Chrome, Firefox та Opera, але ми сподіваємося, що він буде прийнятий всюди, оскільки рішення проблеми плавного скролінгу за допомогою CSS найбільше елегантне, і воно добре підходить до міркування про «прогресивне покращення».

Дотримуйтесь CSS

Інша загальна задача — динамічно позиціювати елемент, в залежності від напрямку скролінгу — знаменитий ефект «sticky».

Скролінг в майбутнє
sticky елемент

Раніше реалізація цього ефекту потребувала написання складного обробника скролінгу, який враховував розміри елементів. Спроби оптимізувати обробник призвели до хитрих затримок у «sticking» та «unsticking», що в результаті вилилось у джитери. JavaScript реалізації також відставали у продуктивності, особливо при використанні Element.getBoundingClientRect().

Не так давно властивість position: sticky була реалізована в CSS. Це дозволило досягти бажаного ефекту, просто вказавши зміщення:

.element {
  position: sticky;
  top: 50px;
}

Решту буде оброблено браузером в найбільш ефективний спосіб. Ви можете протестувати все в цьому codepen. На момент написання цієї статті position: sticky майже повсюдно підтримується (включаючи мобільні браузери), тому якщо ви все ще обробляєте цю проблему в JavaScript — саме час почати дотримуватися реалізації на чистому CSS.

Повний вперед

З точки зору браузера, скролінг — подія, тому в JavaScript ви обробляєте його стандартним addEventListener:

window.addEventListener('scroll', () => {
  const scrollTop = window.scrollY;
  /* doSomething with scrollTop */
});

Але люди, як правило, багато скролять, і якщо подія занадто часто викликається (і спрацьовує на кожному пікселі) — це неминуче призведе до проблем з продуктивністю. Ви можете полегшити роботу браузеру за допомогою техніки, яка називається тротлінг (або дроселювання тактів):

window.addEventListener('scroll', throttle(() => {
  const scrollTop = window.scrollY;
  /* doSomething with scrollTop */
}));

Потім вам потрібно визначити функцію throttle, яка огортатиме функцію прослуховувача та «пришвидшить» її виконання, заборонивши запуск події частіше, ніж раз за бажаний інтервал:

function throttle(action, wait = 1000) {
  let time = Date.now();
  return function() {
    if ((time + wait - Date.now()) < 0) {
        action();
        time = Date.now();
    }
  }
}

Щоб зробити цей процес більш плавним, ви можете об'єднати тротлінг з window.requestAnimationFrame():

function throttle(action) {
  let isRunning = false;
  return function() {
    if (isRunning) return;
    isRunning = true;
    window.requestAnimationFrame(() => {
      action();
      isRunning = false;
    });
  }
}

Звичайно, ви можете вільно використовувати готові реалізації, такі як на Lodash. Подивіться на цей codepen, щоб побачити порівняння між описаним вище рішенням та _.throttle з Lodash.

Насправді, нема ніякого значення, що ви оберете, — просто не забудьте якимсь чином оптимізувати ваш обробник подій скролінгу.

Залишаючись в області перегляду

Трюки на зразок лінивого завантаження зображень або нескінченного скролінгу вимагають від вас з'ясовувати, чи з'явився елемент в області перегляду. Це також робиться всередині прослуховувача подій, а найбільш розповсюдженим рішенням є:

Element.getBoundingClientRect():

window.addEventListener('scroll', () => {
  const rect = elem.getBoundingClientRect();
  const inViewport = rect.bottom > 0 && rect.right > 0 &&
                     rect.left < window.innerWidth &&
                     rect.top < window.innerHeight;
});

Проблема з цим кодом полягає в тому, що кожний виклик getBoundingClientRect спричинятиме reflow, і загальна продуктивність страждатиме. Все стає ще гірше, коли ці виклики робляться всередині обробника подій, і навіть тротлінг у цьому випадку не дуже допомагає.


Подивіться на цю шпаргалку щодо властивостей та методів DOM, які спричиняють reflow.


Це питання було розглянуто у 2016 з введенням Intersection Observer API. Він дозволяє відстежувати перетини між елементом та будь-яким з його предків, не обов'язково в області перегляду браузеру. Більш того, ви можете активувати колбек навіть якщо елемент тільки частково з'явився у представленні хоча б на один піксель.

const observer = new IntersectionObserver(callback, options);

observer.observe(element);

Це API широко підтримується, але деяким браузерам може знадобитись поліфіл. Тим не менш, навіть з поліфілом, поки що це найкраще рішення.

Не скрольте дуже далеко

Якщо вам коли-небудь доводилось реалізовувати спливне вікно або спадне меню, які можна скролити, то ви вже маєте бути знайомі з проблемою ланцюгування скролінгу: якщо продовжувати скролити після закінчення елементу, то вся сторінка почне рухатися.

Скролінг в майбутнє
Візуалізація ланцюгування скролінгу

Ви можете позбавитися від «оверскролінгу» або шляхом маніпулювання властивістю сторінки overflow, або перехоплюючи скролінг на елементі й скасовуючи подію кожного разу, коли досягнуто межі елемента.

Якщо ви оберете JavaScript, впевніться, що обробляєте подію не «scroll», а «wheel», яка спрацьовує кожного разу, коли користувач скролить сторінку за допомогою колеса миші або тачпада:

function handleOverscroll(event) {
  const delta = -event.deltaY;
  if (delta < 0 && elem.offsetHeight - delta > elem.scrollHeight - elem.scrollTop) {
    elem.scrollTop = elem.scrollHeight;
    event.preventDefault();
    return false;
  }
  if (delta > elem.scrollTop) {
    elem.scrollTop = 0;
    event.preventDefault();
    return false;
  }
  return true;
}

На жаль, це рішення не дуже надійне. Крім того, воно може негативно вплинути на продуктивність.

Оверскролінг особливо небезпечний для мобільних пристроїв. Лорен Бріхтер (Loren Brichter) придумав жест «потягнути, щоб оновити» у своєму застосунку Tweetie для iOS, і цей трюк швидко завоював популярність серед UX спільноти: всі основні гравці, включаючи Twitter та Facebook, прийняли його.

Проблема виникла, коли ту саму функцію запровадили в Chrome на Android, що стало перешкодою для кожного, хто реалізовував «потягнути, щоб оновити» у своєму застосунку: якщо ви потягнете сторінку вниз, це спричинить повне оновлення замість завантаження більшої кількості постів.

CSS прийшов на допомогу з новою властивістю: overscroll-behavior. Вона дозволяє контролювати поведінку при досягненні межі скролінгу як для «потягнути, щоб оновити», так і для «ланцюгування скролінгу», а також ОС-специфічних спеціальних ефектів: «світіння (glow)» Android та «канцелярська гумка (rubber band)» Apple.

Тепер проблема, що показана на GIF вище може бути вирішена у Chrome, Opera або Firefox лише за допомогою одного рядка коду:

.element {
  overscroll-behavior: contain;
}

Чесно кажучи, Internet Explorer та Edge реалізовують властивість -ms-scroll-chaining, яка контролює ланцюгування скролінгу, але не обробляє всі випадки. На щастя, згідно з цим тредом, реалізація overscroll-behavior для браузерів Microsoft вже в дорозі.

Останній штрих

Скролінг у сенсорних інтерфейсах — простора тема, яка заслуговує окремої статті. Проте, її також слід тут пригадати, оскільки багато розробників, як правило, ігнорують можливості, які вона надає.

Як часто ви бачите людей навколо вас, що рухають пальцями вгору і вниз по екранах своїх смартфонів? Правильно: це відбувається постійно, і, скоріш за все, ви робите те саме, читаючи цю статтю.


При переміщенні пальця по екрану ви очікуєте одного: плавне, безперервне переміщення вмісту.


«Імпульсний» або «інерційний» скролінг, вперше впроваджений та запатентований Apple, швидко став стандартом UX повсюди.

Можливо ви помічали, що поки імпульсний скролінг обробляється мобільною ОС, він зникає, коли ви намаєтесь скролити всередині елемента на цій сторінці. Для мобільного користувача, що підсвідомо очікує деяку інерцію, це може бути доволі неприємним.

Для цього є рішення на CSS, але це більше хак:

.element {
  -webkit-overflow-scrolling: touch;
}

Чому це хак? По-перше, він працює тільки з вендорним префіксом. По-друге, він працює тільки для сенсорних пристроїв. І, зрештою, якщо браузери не підтримують цю функціональність, можливо варто залишити все як є? В будь-якому випадку, є рішення і ви вільні його використовувати.

Ще одна річ, яку слід враховувати при розробці скролінгу на сенсорних приладах, — продуктивність браузера під час обробки подій touchstart або touchmove. Проблема повністю описана тут. Якщо коротко, сучасні браузери, хоча і знають як обробляти плавний скролінг в окремому потоці, все одно будуть чекати, іноді до 500 мс, на результати обробників, які поміщені на ці події, щоб підготуватися до можливого скасування скролінгу з Event.preventDefault().

Навіть порожній прослуховувач, який ніколи нічого не скасовує, може суттєво вплинути на продуктивність, оскільки браузер все ще чекає на те, що буде викликаний preventDefault.

Щоб явно вказати браузеру, що йому не слід очікувати скасування події, у DOM Living Standard від WHATWG існує дещо неясна особливість. Ознайомтесь з пасивними прослуховувачами подій, які мають досить широку підтримку: ідея полягає в тому, щоб передати необов'язковий аргумент об'єкта прослуховувачу, який говорить браузеру, що подія, яка відбувається, може бути ніколи не скасована. Виклик preventDefault всередині такого обробника нічого не зробить:

element.addEventListener('touchstart', e => {
  /* doSomething */
}, { passive: true });

Існує також поліфіл для браузерів, які не підтримуються. Ефект цього покращення чітко продемонстрований у цьому відео.

Якщо це не поломане, то навіщо його лагодити?

Сильно покладатися на кастомний JavaScript для досягнення ідентичної поведінки для всіх користувачів, більше не виправдовується: вся ідея «крос-браузерної сумісності» відходить у минуле, коли існує так багато властивостей CSS й методів DOM API, що прокладають свій шлях у стандартні реалізації браузерів.

Прогресивне покращення є найкращим підходом для реалізації у ваших проектах нетривіального скролінгу.


Переконайтеся, що ви можете забезпечити максимально можливий мінімальний, але універсально підтримуваний UX, а потім покращите його з урахуванням сучасних функцій браузерів.


Коли це необхідно, використовуйте поліфіли, оскільки вони не створюють залежностей і можуть бути з легкістю видалені, коли з'явиться необхідна підтримка.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 6.2K
Приєднався: 7 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація