Работа мечты в один клик 💼

💭Мечтаешь работать в Сбере, но не хочешь проходить десять кругов HR-собеседований? Теперь это проще, чем когда-либо!
💡AI-интервью за 15 минут – и ты уже на шаг ближе к своей новой работе.
Как получить оффер?
📌 Зарегистрируйся
📌 Пройди AI-интервью
📌 Получи обратную связь сразу же!
HR больше не тянут время – рекрутеры свяжутся с тобой в течение двух дней! 🚀
Реклама. ПАО СБЕРБАНК, ИНН 7707083893. Erid 2VtzquscAwp
В этой статье мы реализуем два типа горизонтальных списков и галерею. Вначале в теоретической части опишем, какое поведение элементов интерфейса хотим получить. В практической части рассмотрим, как воплотить необходимую механику в CSS и JavaScript.
Теория: горизонтальные списки и галерея
Приведённая ниже анимация иллюстрирует горизонтальный список со свободным скроллингом. Структуру можно свободно прокручивать влево и вправо. Список можно слегка оттягивать на крайних элементах. Это даёт подсказку пользователю о границах объекта.

Горизонтальный список со снэппингом – почти то же самое, но текущий элемент списка при тапе заменяется на следующий.

Оба элемента могут применяться я в сочетании, например, список со снэппингом для изображений, относящихся к одному сюжету или пользователю, а список с прокруткой – для быстрого перемещения между такими разделами.
Третий рассматриваемый элемент – галерея. Она похожа на snapping-списки, но каждый элемент занимает всё поле, как в обычном посте Instagram из нескольких фотографий. Под картинками расположен ряд точек – по одной для каждого изображения. Пользователю понятно, что есть другие изображения и какое из них видно в данный момент.

Практическая часть
1. Горизонтальные списки со свободной прокруткой
Для горизонтального списка используем flexbox, которому разрешим горизонтальный скроллинг. Размер элементов списка укажем явно, как и зазор между ними. Поля установим бо́льшими, чем зазор, чтобы видеть, когда мы докрутили до начала или конца списка.
.list {
display: flex;
padding: 20px;
overflow-x: scroll;
}
.item {
height: 224px;
width: 125px;
flex-shrink: 0;
}
.item:not(:last-child) { margin-right: 10px; }
Интерактивное представление и полный код доступны в в сэндбоксе:

Работает, но можно улучшить.
Поведение при прокрутке влево. В некоторых браузерах свайп влево повторяет действие кнопки «Назад». Такое поведение можно предотвратить, установив для overscroll-behavior
значение contain
.
Полосу прокрутки можно скрыть, установив overflow-x
в auto
. Однако при скроллинге полоса вновь появится. Чтобы скрыть полностью, можно установить scrollbar-width
в none
. На момент написания статьи это работало только в Firefox. Для других браузеров придется добавить трюкачества в CSS:
.list { -ms-overflow-style: none; }
.list::-webkit-scrollbar { display: none; }
Плавная прокрутка. В iOS не хватает
стандартной плавной прокрутки (momentum scrolling). Потребуется добавить -webkit-overflow-scrolling:
touch;
.
Предотвращение
вертикальной прокрутки. Мы можем прокручивать
страницу по вертикали. Эту возможность лучше
отключить для пользователей мобильных устройств, добавив к списку touch-action:
pan-x
. Однако если список покрывает всю видимую область просмотра, а на странице есть что-то ещё, эти элементы станут недоступны. То есть такой вариант надо использовать с осторожностью.
В результате наших преобразований соответствующая часть CSS теперь будет иметь следующий вид:
.list {
display: flex;
overflow-x: scroll;
padding: 20px;
/* Предотвращает случайное использование "Назад"*/
overscroll-behavior: contain;
/* Скрывает полосу прокрутки */
scrollbar-width: none;
-ms-overflow-style: none;
/* Плавная прокрутка на iOS */
-webkit-overflow-scrolling: touch;
/* Отключает вертикальный скроллинг для тач-устройств */
touch-action: pan-x;
}
/* Скрывает полосу прокрутки */
.list::-webkit-scrollbar {
display: none;
}
.item {
height: 224px;
width: 125px;
flex-shrink: 0;
}
.item:not(:last-child) {
margin-right: 10px;
}
/* фикс для отступов в конце списка */
.item:last-child {
position: relative;
}
.item:last-child::after {
position: absolute;
left: 100%;
height: 1px;
width: 20px;
display: block;
content: "";
}
А результат выглядит вот так (там же полный код примера):

2. Горизонтальный список со снэппингом
Для описания этого типа списков отталкиваемся от списка со свободной прокруткой. Сначала сообщаем списку, когда нужно останавливаться:
.list {
scroll-snap-type: x mandatory;
}
Элементам списка добавляем опцию scroll-snap-align: start
. Чтобы дать подсказку, следующий элемент может немного «выглядывать» из-за края экрана. Для этого достаточно установить отступ прокрутки: scroll-padding-inline-start:
20px
. Если хочется свайпнуть
одним движением несколько элементов,
можно добавить scroll-snap-stop: always
к элементам списка, но не все браузеры пока
такое поддерживают.
Код полностью также выложен на Codesandbox:

3. Галерея в стиле Instagram
Если мы сделаем наш предыдущий список таким же широким, как область прокрутки, и удалим отступы, он будет выглядеть и вести себя примерно так же, как галерея Instagram. За исключением маленьких точек. Без точек будет выглядеть так:

Но как мы поясняли в теоретической части, эти маленькие точки – полезный графический приём. Получается, что нам нужно следить за тем, какой из элементов списка сейчас виден. Наиболее простой способ – использовать IntersectionObserver.
Составим список точек, каждая из которых соответствует одной из картинок. Когда она пролистывается в списке, мы получаем индекс элемента и устанавливаем индикаторную точку с соответствующим индексом в активное положение.
Вот как это будет выглядеть:
// ссылки на элементы DOM
const list = document.querySelector('.list');
const items = Array.from(document.querySelectorAll('.item'));
const indicators = Array.from(document.querySelectorAll('.indicator'));
// создание наблюдателя
const observer = new IntersectionObserver(onIntersectionObserved, {
root: list,
threshold: 0.6
});
// наблюдаем за каждым элементом
items.forEach(item => {
observer.observe(item);
});
// когда observer обнаруживает изменение записи
// (пункт, входящий в список)
// и эта запись пересекается,
// получаем индекс пересекающегося элемента
// устанавливаем нужный индикатор в активное положение
function onIntersectionObserved(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const intersectingIndex = items.indexOf(entry.target);
activateIndicator(intersectingIndex);
}
});
}
function activateIndicator(index) {
indicators.forEach((indicator, i) => {
indicator.classList.toggle('active', i === index);
});
}
Полный результат также доступен в сэндбоксе:

Заключение
Мы разобрали несколько вариаций построения горизонтальных списков изображений. У приведённых решений есть и свои недостатки, и преимущества. Поскольку используется нативная прокрутка, нет возможности настроить способ движения, мы не можем контролировать «липкость» ползунка (в этом случае правильнее было бы использовать решение на JavaScript). Кроме того, в нашем решении мы использовали не самые стандартные подходы в CSS. Однако объём кода невелик, и он работает даже в старых браузерах.
Комментарии