Перевод «Practical CSS Scroll Snapping» Max Kohler
Спецификация CSS Scroll Snap позволяет привязывать положение прокрутки к определённым элементам или местоположению после того, как пользователь прокрутил страницу или элемент. Это отличный способ для реализации следующих решений:
Браузерная поддержка и базовое использование
С тех пор, как CSS Scroll Snap был представлен в 2016 году, поддержка браузерами существенно улучшилась. К 2018 году Google Chrome (69+), Firefox, Edge, и Safari поддерживали какую-либо версию этого модуля. Согласно CanIUse, на данный момент технология поддерживается всеми современными браузерами.
Примечание от переводчика
Работоспособность приведённых в статье примеров CodePen проверена в мобильных браузерах Firefox и Chrome Canary (в Chrome Stable некоторые еще не сработали)
Использование Scroll Snapping заключается в установке свойства scroll-snap-type для элемента-контейнера и свойства scroll-snap-align для дочерних элементов. Прокрутка элемента-контейнера осуществляться с привязкой к дочерним элементам, которые вы определили. В этом самом простом случае это будет выглядеть следующим образом:
<div class="container">
<section class="child"></section>
<section class="child"></section>
<section class="child"></section>
<p>...</p>
</div>
.container {
scroll-snap-type: y mandatory;
}
.child {
scroll-snap-align: start;
}
Данный способ отличается от первой версии спецификации, которая позволяла вам устанавливать точки привязки вручную, используя ключевое слово repeat
.
.container {
/* OLD */
scroll-snap-points-y: repeat(300px);
}
Такой подход был довольно ограниченным, поскольку позволял устанавливать точки привязки только через определённые равные промежутки, что не позволяло реализовать интерфейс, который работал бы с элементами разных размеров. Например, если элементы меняются в зависимости от размера экрана, это может вызвать проблемы. На данный момент все современные браузеры поддерживают новый способ, который ориентируется не на расстояние, а на сами элементы.
Вы можете использовать оба этих метода (если ваш макет позволяет это) для поддержки старых версий браузеров:
.container {
scroll-snap-type: mandatory;
scroll-snap-points-y: repeat(300px);
scroll-snap-type: y mandatory;
}
.child {
scroll-snap-align: start;
}
Ещё более гибкий вариант – использовать новый синтаксис, а для браузеров, которые его не поддерживают, использовать полифилл. Именно этот способ я использую в примерах ниже.
Свойства контейнера
Как и в случае с любым другим свойством, неплохо бы познакомиться со значениями, которые оно принимает. Свойства из спецификации "scroll snap
" применяются как к родителю, так и к дочерним элементам, с определёнными значениями для каждого. Подобно тому, как в Flexbox или CSS Grid родитель становится "flex-" или "grid-" контейнером, можно сказать, что здесь родитель становится scroll-контейнером.
Далее представлены свойства и значения для родительского контейнера и описание принципа их работы.
scroll-snap-type: "mandatory" vs "proximity"
Значение "mandatory
" определяет поведение, при котором всякий раз, когда пользователь прекращает прокрутку, браузер должен возвращать её к точке привязки.
Значение "proximity
" менее строгое – оно означает, что браузер может возвращаться к точке привязки, если ему это покажется уместным. Из моего опыта, если задано это значение, срабатывание происходит, если прокрутка остановилась в пределах нескольких сотен пикселей от точки привязки.
Используя данное свойство в своей работе, я обнаружил, что значение "mandatory
" делает работу интерфейса более последовательной и предсказуемой, но как говорится в спецификации, оно также может быть достаточно опасным. Представьте ситуацию, в которой элемент внутри прокручиваемого контейнера по высоте больше области видимости:
Если для контейнера задано scroll-snap-type: mandatory
, он всегда будет привязан либо к верху данного элемента, либо к верху элемента, расположенного ниже, делая невозможным зафиксировать прокрутку в середине высокого элемента.
scroll-padding
По умолчанию, при прокрутке дочерние элементы будут прижиматься к самым краям контейнера. Вы можете изменить это, задав для контейнера свойство scroll-padding
. Оно имеет такой же синтаксис, что и свойство padding
.
Это может быть полезно, если в вашем макете есть элементы, которые могут мешать содержимому, например фиксированный заголовок.
Свойства дочерних элементов
Теперь давайте перейдём к свойствам дочерних элементов
scroll-snap-align
Данное свойство позволяет указать, какая сторона элемента должна прижиматься к контейнеру. У свойства есть три возможных значения: "start
", "center
" и "end
".
Значения определяются относительно направления прокрутки. Если вы прокручиваете элемент по вертикали, "start
" подразумевает верхний край элемента, если по горизонтали – левый. Значения "center
" и "end
" работают по тому же принципу. Для каждого направления прокрутки можно устанавливать своё значение, разделив их пробелом.
scroll-snap-stop: "normal" vs "always"
По умолчанию, привязка прокрутки происходит только тогда, когда пользователь прекращает прокрутку. Это значит, что за время прокрутки некоторые точки привязки потенциально могут быть пропущены.
Это поведение можно изменить, задав любому дочернему элементу свойство "scroll-snap-stop: always
". Это заставляет прокручиваемый контейнер остановиться на этом элементе, прежде чем пользователь сможет продолжить скролить.
Давайте рассмотрим несколько примеров использования технологии "Scroll Snap".
Пример 1: Вертикальный список
Чтобы в вертикальном списке задать привязку для каждого дочернего элемента, нужна всего пара строк CSS. Сперва мы говорим контейнеру привязываться к элементам вдоль вертикальной оси:
.container {
scroll-snap-type: y mandatory;
}
Затем мы определим точки привязки. Здесь мы указываем, что точкой привязки должен стать верх каждого элемента:
.child {
scroll-snap-align: start;
}
Пример 2: Горизонтальный слайдер
Чтобы сделать горизонтальный слайдер, мы говорим контейнеру привязываться к элементам при прокрутке по оси X. Дополнительно используем свойство "scroll-padding
", чтобы убедиться, что дочерние элементы привязаны к центру контейнера.
.container {
scroll-snap-type: x mandatory;
scroll-padding: 50%;
}
Затем мы сообщаем контейнеру, к каким точкам привязаться. Чтобы расположить элементы посередине родителя, мы определяем центр каждого элемента как точку привязки.
.child {
scroll-snap-align: center;
}
Пример 3: Вертикальная прокрутка полноэкранных блоков
Мы можем установить точки привязки непосредственно на элементе:
html { /* body won't work ?\_(?)_/? */
scroll-snap-type: y mandatory;
}
Затем мы задаем каждому блоку размер области видимости и определяем точку привязки по верхнему краю блока:
section {
height: 100vh;
width: 100vw;
scroll-snap-align: start;
}
Пример 4: Горизонтальная прокрутка полноэкранных блоков
Здесь используется та же концепция, что и в версии с вертикальной прокруткой, но с установкой точек привязки на оси X.
body {
scroll-snap-type: x mandatory;
}
section {
height: 100vh;
width: 100vw;
scroll-snap-align: start;
}
Пример 5: Двухмерная сетка изображений
Привязка прокрутки (scroll snapping) может работать в двух измерениях одновременно. И снова-таки, мы можем установить непосредственно на элементе:
.container {
scroll-snap-type: both mandatory;
}
Затем мы определяем левый верхний угол каждой плитки как точку привязки:
.tile {
scroll-snap-align: start;
}
Некоторые мысли об удобстве для пользователей
Связывать с прокруткой – опасное занятие. Поскольку это один из основных способов взаимодействия с приложением, любое изменение этого поведения может вызывать раздражение – термин "scrolljacking" используется для обозначения подобного рода явлений.
Достоинством реализованного в CSS управления привязкой прокрутки является то, что вы не получаете прямой контроль над позицией прокрутки. Вместо этого, вы просто даёте браузеру список позиций для привязки таким образом, который соответствует платформе, способу ввода и пользовательским предпочтениям. Это значит, что поведение прокрутки, которое вы создаёте, на любой платформе будет ощущаться как нативное (то есть, с использованием тех же анимаций и т.д).
Считаю это ключевым преимуществом привязки прокрутки, реализованной с помощью CSS, перед JavaScript библиотеками, предлагающими схожую функциональность.
По моим личным ощущениям, это работает достаточно хорошо, особенно на мобильных устройствах. Возможно, из-за того, что "scroll snapping" уже является частью встроенного в мобильные платформы UI. Вспомните главный экран на iOS и Android устройствах – это, по сути, горизонтальный слайдер с точками привязки. Взаимодействие в Chrome на Android особенно приятно, потому что воспринимается как обычная прокрутка, но область видимости всегда останавливается на точке привязки:
Для реализации такого поведения, определённо нужно проводить некоторые математические вычисления. Благодаря CSS Scroll Snapping мы добиваемся такого же результата без этого.
Конечно, мы не должны создавать точки привязки на всём подряд. Например, страницы со статьями прекрасно обойдутся и без них. Но я думаю, что они могут существенно улучшить удобство взаимодействия с приложение, если будут использованы в нужный момент – галереи изображений, слайдшоу кажутся хорошими кандидатами.
Заключение
Если относиться к этому ответственно, привязка прокрутки может стать полезным инструментом. Точки привязки CSS позволяют вам включиться во встроенный в браузер процесс работы с прокруткой без ущерба плавности интерфейса. С появлением JavaScript API (потенциально на горизонте) они станут еще более мощными.
От переводчика
Также, перевёл статью на MDN «Базовые концепции CSS Scroll Snap»
andreymal
При прокрутке колёсиком примера https://codepen.io/team/css-tricks/pen/yLLqqgP в моём хроме почему-то проскакивается через один элемент, то есть с One переключается на Two и потом сразу же на Three без задержек, даже если я прокручу колёсико всего один раз. В фаерфоксе всё работает нормально. Проблема в хроме или во мне? (Или проблема в том, что у меня не Canary?)
hisbvdis Автор
Странно.
Проверил на Chrome 80, 81 и 84.
Поведение немного отличается, но именно описанной проблемы не встретил.
Organizer
Та-же проблема)) Оберни контент в div с height:100vh и overflow-y:scroll и будет тебе счастье)
(видимо с тегом html оно не робит) (и в стилях замени html на твой div)
Farmatique
А платформа какая? Помнится, были проблемы схожего характера на Маках с их Magic mouse (кажется так называется) и тачпадами
andreymal
Линукс, обычные иксы, обычная мышка. На других платформах ещё не пробовал