Kodokon kodokon.com

Современные взаимодействия: scroll-snap, view transitions

Организуй магнитную прокрутку, переходы между состояниями, которые анимирует сам браузер, и строгое уважение к prefers-reduced-motion.

11 мин · 3 вопросов

Открыть этот урок в Kodokon

В scroll snap участвуют два актёра: контейнер задаёт snapport (свою видимую область, ужатую на scroll-padding) и стратегию через scroll-snap-type - proximity притягивает только вблизи точки привязки, а mandatory гарантирует, что ты всегда окажешься на одной из них, даже после программной прокрутки. Дети объявляют свою область выравнивания через scroll-snap-align (её можно подстроить через scroll-margin). Малоизвестная тонкость: при быстром свайпе браузер имеет полное право перескочить через несколько точек привязки; scroll-snap-stop: always заставляет его останавливаться на каждой - незаменимо для карусели в духе сторис.

CSS
.carousel {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: 1rem;
}
.carousel > article {
  flex: 0 0 80%;
  scroll-snap-align: center;
  scroll-snap-stop: always;
}
Магнитная карусель: каждый слайд центрируется сам, и ни один свайп не может его пропустить.

API View Transitions анимирует смену состояния DOM без единой библиотеки. document.startViewTransition(callback) делает визуальный снимок текущего состояния, выполняет твой колбэк (который меняет DOM), затем анимирует переход между старым и новым состоянием. Под капотом браузер строит поверх страницы дерево псевдоэлементов: ::view-transition содержит элементы ::view-transition-group, каждый из которых держит ::view-transition-old (снимок, замещаемое содержимое, сравнимое с картинкой) и ::view-transition-new (живое представление). По умолчанию - перекрёстное затухание. Во время перехода страница не интерактивна.

JAVASCRIPT
function applyUpdate(updateDom) {
  if (!document.startViewTransition) {
    updateDom();
    return;
  }
  document.startViewTransition(() => updateDom());
}
Прогрессивное улучшение: без поддержки обновление просто применяется без анимации.

Настоящая сила приходит с view-transition-name: именованный элемент выносится в собственную группу перехода. Если элемент с тем же именем существует до и после, браузер сам интерполирует положение и размер между ними - эффект морфинга из миниатюры в полноэкранный вид, без единой строки анимации на JavaScript. Каждое имя должно быть уникальным на странице в любой момент времени, иначе переход отменяется. Псевдоэлементы стилизуются обычными CSS-анимациями.

CSS
.hero-thumb { view-transition-name: hero; }

::view-transition-old(hero),
::view-transition-new(hero) {
  animation-duration: 0.4s;
  animation-timing-function: ease-in-out;
}
Именованная группа hero перехвачена, и длительность её анимации настроена вручную.

Остаётся обязанность по доступности: prefers-reduced-motion: reduce сигнализирует о пользователе, чувствительном к движению (вестибулярные нарушения, мигрени). Уменьшить не значит убрать: затухание по opacity остаётся приемлемым; нейтрализовать нужно крупные перемещения, зумы и параллакс. Защитное глобальное переопределение в CSS ты уже знаешь (околонулевые длительности, разбирали в предыдущем модуле); view transitions добавляют второй фронт: со стороны JavaScript спрашивай window.matchMedia("(prefers-reduced-motion: reduce)") перед запуском перехода или анимированной прокрутки.

JAVASCRIPT
const reduceMotion = window.matchMedia(
  '(prefers-reduced-motion: reduce)'
).matches;

if (reduceMotion || !document.startViewTransition) {
  updateDom();
} else {
  document.startViewTransition(() => updateDom());
}
Страховка на JavaScript: обновление применяется без морфинга, когда пользователь просит меньше движения.

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. Что делает scroll-snap-stop: always на элементе карусели?
    • Отключает инерцию прокрутки на тач-экранах
    • Заставляет останавливаться на каждой точке привязки, даже при быстром свайпе
    • Блокирует прокрутку по перпендикулярной оси
  2. Что захватывает document.startViewTransition() перед выполнением твоего колбэка?
    • Глубокую копию текущего DOM
    • Визуальный снимок старого состояния, воспроизводимый в ::view-transition-old
    • Сериализованный HTML всей страницы
  3. Как уважить prefers-reduced-motion перед запуском view transition из JavaScript?
    • Никак: эта настройка читается только в CSS
    • Проверить window.matchMedia('(prefers-reduced-motion: reduce)').matches и применить обновление без startViewTransition, если совпало
    • Передать опцию { motion: 'reduce' } в startViewTransition