В этой статье мы реализуем два типа горизонтальных списков и галерею. Вначале в теоретической части опишем, какое поведение элементов интерфейса хотим получить. В практической части рассмотрим, как воплотить необходимую механику в CSS и JavaScript.
Теория: горизонтальные списки и галерея
Приведённая ниже анимация иллюстрирует горизонтальный список со свободным скроллингом. Структуру можно свободно прокручивать влево и вправо. Список можно слегка оттягивать на крайних элементах. Это даёт подсказку пользователю о границах объекта.
![Горизонтальный список со свободной прокруткой (free-scrolling horizontal lists)](https://media.proglib.io/posts/2020/05/10/59f58da32342b2c95ae65a17b5d557a5.gif)
Горизонтальный список со снэппингом – почти то же самое, но текущий элемент списка при тапе заменяется на следующий.
![Горизонтальный список со снэппингом (snapping horizontal lists)](https://media.proglib.io/posts/2020/05/10/d9bfd64641ba9ae33e8e03ba9422e395.gif)
Оба элемента могут применяться я в сочетании, например, список со снэппингом для изображений, относящихся к одному сюжету или пользователю, а список с прокруткой – для быстрого перемещения между такими разделами.
Третий рассматриваемый элемент – галерея. Она похожа на snapping-списки, но каждый элемент занимает всё поле, как в обычном посте Instagram из нескольких фотографий. Под картинками расположен ряд точек – по одной для каждого изображения. Пользователю понятно, что есть другие изображения и какое из них видно в данный момент.
![Пример галереи](https://media.proglib.io/posts/2020/05/10/cc348e6ad91dc8cf2dde94a3bea0ed4b.gif)
Практическая часть
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; }
Интерактивное представление и полный код доступны в в сэндбоксе:
![📸 Как сделать галерею в стиле Instagram](https://media.proglib.io/posts/2020/05/10/647bdf56efa53421915441cd2d396bae.jpg)
Работает, но можно улучшить.
Поведение при прокрутке влево. В некоторых браузерах свайп влево повторяет действие кнопки «Назад». Такое поведение можно предотвратить, установив для 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: "";
}
А результат выглядит вот так (там же полный код примера):
![📸 Как сделать галерею в стиле Instagram](https://media.proglib.io/posts/2020/05/10/ab6699eb1c019d882b8dc5192cd279b1.jpg)
2. Горизонтальный список со снэппингом
Для описания этого типа списков отталкиваемся от списка со свободной прокруткой. Сначала сообщаем списку, когда нужно останавливаться:
.list {
scroll-snap-type: x mandatory;
}
Элементам списка добавляем опцию scroll-snap-align: start
. Чтобы дать подсказку, следующий элемент может немного «выглядывать» из-за края экрана. Для этого достаточно установить отступ прокрутки: scroll-padding-inline-start:
20px
. Если хочется свайпнуть
одним движением несколько элементов,
можно добавить scroll-snap-stop: always
к элементам списка, но не все браузеры пока
такое поддерживают.
Код полностью также выложен на Codesandbox:
![📸 Как сделать галерею в стиле Instagram](https://media.proglib.io/posts/2020/05/10/84a2815b9baeb67026979bb375a3415e.jpg)
3. Галерея в стиле Instagram
Если мы сделаем наш предыдущий список таким же широким, как область прокрутки, и удалим отступы, он будет выглядеть и вести себя примерно так же, как галерея Instagram. За исключением маленьких точек. Без точек будет выглядеть так:
![📸 Как сделать галерею в стиле Instagram](https://media.proglib.io/posts/2020/05/10/05b3204692441d4d89e5ef4ea5236554.jpg)
Но как мы поясняли в теоретической части, эти маленькие точки – полезный графический приём. Получается, что нам нужно следить за тем, какой из элементов списка сейчас виден. Наиболее простой способ – использовать 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);
});
}
Полный результат также доступен в сэндбоксе:
![📸 Как сделать галерею в стиле Instagram](https://media.proglib.io/posts/2020/05/10/24498f5db0c74fc934298cf8e5f7a4d7.jpg)
Заключение
Мы разобрали несколько вариаций построения горизонтальных списков изображений. У приведённых решений есть и свои недостатки, и преимущества. Поскольку используется нативная прокрутка, нет возможности настроить способ движения, мы не можем контролировать «липкость» ползунка (в этом случае правильнее было бы использовать решение на JavaScript). Кроме того, в нашем решении мы использовали не самые стандартные подходы в CSS. Однако объём кода невелик, и он работает даже в старых браузерах.
Комментарии