Перейти к содержанию

Управляемая прокрутка с scroll snap

Горизонтальная лента может останавливаться в случайном положении: край карточки уже исчез, а следующая видна только наполовину. Scroll snap позволяет обозначить предпочтительные точки, к которым браузер привязывает завершение движения. Он не создаёт сами карточки, не переключает выбранный курс и не превращает список в полноценную карусель с новым управлением.

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

Прокрутка существует до привязки

Сначала нужен контейнер, в котором действительно есть лишняя ширина. Само scroll-snap-type не создаёт переполнение и не назначает размеры детям. В нашей ленте раскладку задаёт Flexbox: карточки стоят в ряд, не сжимаются до незаметных полос и вместе занимают больше доступного окна. Затем overflow-x: auto предоставляет обычную прокрутку этого ряда.

В index.html замените только открывающий тег ul.course-list и добавьте абзац непосредственно перед ним. Шесть прежних элементов li и их содержание сохраняются без перестановки. Закрывающий тег списка остаётся на месте.

<p class="rail-note">Диагностическая лента: прокрутите список по горизонтали.</p>
<ul class="course-list course-list--rail" tabindex="0" aria-label="Лента шести курсов">

Это фрагмент изменения начала списка, а не полный HTML, который нужно записать вместо документа. В конец styles.css добавьте следующий блок с отдельным модификатором. Его действие ограничено лентой; исходный селектор сетки остаётся, чтобы режим можно было явно удалить на следующем шаге.

@layer components {
.layout-lab .course-list.course-list--rail { display: flex; overflow-x: auto; scroll-snap-type: x proximity; scroll-padding-inline: .75rem; padding: .75rem; }
.layout-lab .course-list--rail > .course-slot { flex: 0 0 min(85%, 22rem); display: block; scroll-snap-align: start; }
.layout-lab .course-list--rail .course-card { display: grid; grid-template-rows: auto auto 1fr auto; }
.layout-lab .rail-note { margin-block-start: .5rem; }
}

У контейнера с двумя классами более специфичный селектор выбирает display: flex. Слоты ленты становятся обычными обёртками, а внутренние ряды каждой карточки рассчитываются самостоятельно. Это отключает зависимость от общего subgrid для данного режима. Нельзя просто убрать перенос столбцов, сохранив ожидание общих вертикальных дорожек от внешней Grid-сетки, которой здесь больше нет.

Предпочтительное место остановки

Контейнер получает scroll-snap-type: x proximity, а дети — scroll-snap-align: start. Первая запись включает предпочтительную привязку по горизонтальной оси, вторая обозначает начало карточки как место согласования с областью просмотра. Ожидается, что после некоторых движений браузер доведёт ближайшую подходящую карточку к этой границе.

Значение proximity не обещает одинаковое расстояние срабатывания во всех браузерах. Движок выбирает привязку, когда сочтёт её достаточно близкой. Если пользователь остановился далеко от точки, промежуточное положение может сохраниться. Это мягкое предпочтение, которое оставляет больше свободы чтения. Подмена его обещанием «каждый жест обязательно покажет ровно следующую карточку» была бы неверной.

Вариант mandatory задаёт более строгую необходимость привязки после завершения прокрутки. Для самостоятельного опыта можно временно заменить только это слово и сравнить ощущение. Затем верните proximity. Строгий вариант не всегда удобен для содержания, превышающего видимую область, и не делает интерфейс автоматически понятнее. Выбор должен учитывать возможность прочитать весь элемент, а не только аккуратность его начала.

Браузер также ограничен максимальным диапазоном прокрутки. Начало последней карточки может не попасть ровно на начало окна, если после неё недостаточно свободного места. Это не означает, что свойство игнорируется. Согласование подчинено реальной области движения. Наш пример не добавляет длинный пустой хвост ради полного совпадения последней точки, потому что такая геометрия была бы отдельным продуктовым выбором.

Отступ окна и размер элемента

scroll-padding-inline: .75rem описывает предпочитаемую внутреннюю область согласования прокрутки, а обычный padding создаёт реальное место у краёв списка. Эти две записи решают разные задачи. Если изменить только padding, положение snap-границы не обязательно получит ожидаемую поправку; если изменить только scroll-padding, физическое пространство содержимого не появится само.

Базис слота равен min(85%, 22rem). Он выбран так, чтобы карточка не занимала бесконечно большую ширину на широком окне, а на узком оставляла небольшой намёк на продолжение. Это предпочтение учебного макета, а не измеренная оптимальная ширина. Если надпись станет длиннее, карточка может вырасти по высоте; не ограничиваем высоту ленты ради декоративного одинакового прямоугольника.

Сам слот не сжимается, поскольку коэффициент shrink равен нулю. Поэтому лишние карточки действительно уходят в область прокрутки. Если оставить стандартное сжатие, все элементы могут попытаться поместиться в доступное место и исчезнет причина горизонтального движения. Разбор прокрутки должен начинаться с реальных размеров детей, а не с одного нового свойства контейнера.

Основные обязанности контейнера и точек объясняет MDN о scroll snap. В текущем документе ось X выбрана для горизонтальных русских строк. Для другого направления письма требуется отдельно проверить геометрию и значения начала; мы не объявляем этот диагностический вариант универсальной локализованной каруселью.

Управление остаётся обычным

Лента получила возможность фокуса и видимое пояснение. Внутри находятся обычные ссылки на существующие оглавления. Нет автопрокрутки, таймера и скрытого выбранного слайда. Поэтому мы не добавляем искусственные атрибуты карусели, которые обещали бы дополнительное поведение. Scroll snap регулирует остановку, а действия пользователя остаются механизмами браузера.

Позже нужно вручную проверить доступ к крайним карточкам с клавиатуры и на сенсорном устройстве. Наличие tabindex не означает одинаковые клавиши прокрутки во всех окружениях. Фокус ссылки должен оставаться видимым, а положение ленты не должно лишать доступа к её назначению. Эти наблюдения в подготовке серии не выполнялись; сохранён полный источник для их последующего проведения.

Если браузер не поддерживает snap, обычная горизонтальная прокрутка должна сохраняться: её свойства заданы отдельно. Это хороший пример ограниченного улучшения. Список не исчезает из HTML и не становится бесполезной кнопкой из-за отсутствующей привязки. Однако сам горизонтальный режим всё равно требует оценки удобства; поддержка свойства не является аргументом применять ленту везде.

Не перехватывайте колесо мыши только ради принудительного движения ленты. Такой обработчик изменил бы обычный маршрут прокрутки страницы и потребовал бы отдельного решения для разных устройств. Наша лента сохраняет стандартный механизм браузера без дополнительного JavaScript.

После опыта сохраните данный снимок отдельно. Для следующего урока снимите модификатор course-list--rail, удалите абзац и атрибуты диагностической ленты, а также весь её дополнительный CSS-блок. Вернётся состояние после двадцать пятого урока: обычная сетка и таблица. На этой основе начнём управлять геометрией обложки, сохраняя доступ к курсам без горизонтального режима.

Полное состояние этого шага находится в layout-lab/lesson-26 внутри исходников продолжения. Каждый снимок открывается отдельно; подключать CSS разных уроков одновременно не нужно.

Оглавление курса.