Назад в Центр знаний
Платформа

View Transitions API: состояние и навигация

Согласуйте document.startViewTransition с состоянием DOM, ready и finished, историей, отменой и режимом уменьшенного движения.

BotBrowser Team

Документация

Нужна структурированная документация по теме Платформа?

Эта статья относится к редакционной библиотеке. Для пошаговой настройки, справки и постоянных обновлений переходите сразу в соответствующий раздел docs.

View Transitions API делает переход внутри одного документа непрерывным, но анимация является лишь частью контракта. BotBrowser может повторить разрешённый сценарий и записать ограниченные состояния DOM, жизненного цикла и настроек; он не добавляет API в неподдерживаемый браузер и не подтверждает, что объявляет вспомогательная технология. Используйте этот материал вместе с историей и приватностью Navigation API и настройками media query и контролем пользователя, если переход одновременно меняет маршрут или режим движения.

TL;DR

  • Вызывайте document.startViewTransition() вокруг одного синхронного изменения DOM; callback должен быть идемпотентным.
  • transition.ready означает готовность захваченных псевдоэлементов к анимации, а transition.finished означает завершённую визуальную фазу.
  • Отклонённое обновление или отменённый переход является управляемым откатом, а не доказательством ошибки маршрута. Новый DOM должен оставаться рабочим.
  • Сначала обновите состояние приложения, затем измеряйте новый макет; не записывайте одну навигацию или геометрию дважды.
  • При prefers-reduced-motion: reduce сохраните изменение состояния и запись истории, но отключите или сократите анимацию.

Содержание

Модель жизненного цикла

Спецификация CSS View Transitions описывает снимок старого рендера, callback обновления и новый рендер, который можно анимировать псевдоэлементами. Точка входа это document.startViewTransition(updateCallback). Она сразу возвращает объект ViewTransition, хотя callback и снимки появятся позже. Это дескриптор жизненного цикла, а не обещание завершённого маршрута.

Явно разделите состояния:

Поток от фиксации DOM через ready, animation, finished и abort к откату уменьшенного движения

СостояниеЗначениеДопустимая запись
requestedПриложение приняло запросnavigationId, маршрут, настройка движения
updatingCallback меняет принадлежащий DOMupdateStarted: true
readyНовые псевдоэлементы доступныready: true, без утверждения о завершённой отрисовке
animatingИдёт анимация или мгновенный путьanimationMode
finishedПереход стабилизирован или пропущенfinished: true
abortedСнимок, обновление или анимация остановленыabortReason, состояние отката

Так requested, ready и finished не превращаются в три навигации: это фазы одного события. Сохраняйте один navigationId и один конечный результат. Браузер может пропустить анимацию, но зафиксировать DOM; finished не означает, что пользователь видел ненулевую длительность.

Проверяйте метод на границе. При отсутствии document.startViewTransition выберите то же обновление маршрута без визуального снимка. История не должна зависеть от наличия улучшения.

Зафиксируйте DOM один раз

Callback обновления владеет новым DOM. Сделайте его синхронным и коротким. Данные загрузите до перехода или сначала зафиксируйте состояние загрузки. Второй клик нужно игнорировать либо выдать ему осознанный navigationId; две записи истории недопустимы.

let navigationId = 0;

function showRoute(route, render) {
  const id = ++navigationId;
  const commit = () => {
    render(route);
    document.documentElement.dataset.route = route;
  };

  if (!document.startViewTransition) {
    commit();
    return Promise.resolve({ id, mode: 'fallback', outcome: 'committed' });
  }

  const transition = document.startViewTransition(commit);
  return transition.finished.then(
    () => ({ id, mode: 'view-transition', outcome: 'finished' }),
    error => ({ id, mode: 'view-transition', outcome: 'aborted', error: error.name })
  );
}

Рендерьте из неизменяемой записи маршрута и обновляйте хранилище один раз. Наблюдатель, который меняет другой элемент во время снимка, смешивает две страницы: это гонка приложения, а не повод бесконечно повторять переход. Используйте view-transition-name только для семантически одного объекта; дубликат имени может сорвать снимок. Безымянный вариант должен оставаться читаемым.

Обработайте ready, finished и abort

transition.ready разрешается, когда дерево псевдоэлементов готово к анимации. Это место для класса, но не доказательство, что человек увидел новую страницу. transition.finished разрешается после анимации или пути без неё. Используйте его для очистки и снятия блокировки, а не для второго события маршрута.

const transition = document.startViewTransition(updateRoute);
const record = { navigationId, ready: false, outcome: 'updating' };

transition.ready.then(
  () => {
    record.ready = true;
    record.outcome = 'animating';
  },
  error => {
    record.outcome = 'aborted';
    record.abortReason = error.name;
  }
);

transition.finished.then(
  () => {
    record.outcome = 'finished';
    releaseRouteLock();
  },
  error => {
    record.outcome = 'aborted';
    record.abortReason = error.name;
    releaseRouteLock();
  }
);

Если callback выбросил исключение, оставьте документ рабочим и покажите существующее сообщение ошибки или повтор. Разделяйте domCommitted и animationOutcome: успешное обновление не нужно откатывать из-за ошибки анимации. Разрешённые причины: unsupported, duplicate-name, update-error, reduced-motion, superseded.

При новой навигации старый callback больше не должен записывать результат. Новый маршрут рендерится из текущего состояния, а не из устаревшего снимка.

Не считайте историю и измерения дважды

pushState() или Navigation API решает, создаётся ли запись; startViewTransition() добавляет только визуальный слой. Выполните операцию истории один раз и передайте маршрут в callback. Событие popstate должно сохранять решение роутера о back/forward, не добавляя запись из-за начавшейся анимации.

Руководство по Navigation API и истории объясняет владельца записи. Используйте его navigationId, а не второй счётчик. Один маршрут имеет одну попытку перехода и один конечный визуальный результат. При отсутствии API оставьте ту же операцию и mode: "fallback".

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

function measureOnce(phase, navigationId, element) {
  const rect = element.getBoundingClientRect();
  return {
    navigationId,
    phase,
    rect: {
      top: Math.round(rect.top),
      left: Math.round(rect.left),
      width: Math.round(rect.width),
      height: Math.round(rect.height),
    },
  };
}

Координаты зависят от прокрутки, масштаба, шрифтов и маршрута. Сохраняйте viewport и offset visualViewport, когда геометрия является критерием; не превращайте её в идентификатор. Для back/forward пусть историческое событие выберет запись, а новую заявку пометьте superseded до рендера.

Создайте откат для уменьшенного движения

Справка MDN о prefers-reduced-motion описывает пользовательскую настройку. Проверяйте её до длинного перемещения. Состояние, фокус, маршрут и история сохраняются; меняется только анимация.

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.18s;
  animation-timing-function: ease-out;
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation-duration: 0s;
    animation-iteration-count: 1;
  }
}

Читайте настройку при каждой навигации. Ветка JavaScript без API должна выполнять тот же commit. Записывайте animationMode: "instant" и motionPreference: "reduce", но не сохраняйте это как профильную идентичность.

Переход не создаёт ловушку фокуса, доступное имя или объявление для скринридера. Перемещайте фокус по контракту маршрута и отдельно проверяйте клавиатуру и вспомогательную технологию. Заголовок новой страницы должен появиться в DOM до объявления маршрута готовым.

Запустите ограниченную матрицу

Используйте один same-origin сценарий, стабильные данные и новый профиль для каждой строки.

СтрокаУсловиеОжидаемый результатПроверка
AAPI есть, обычное движениеDOM, ready и finished ровно разОсновной цикл
BAPI отсутствуетТот же DOM и история, mode: fallbackСовместимость
CДублированное имяРабочее восстановление и причина abortОшибка снимка
Dprefers-reduced-motion: reduceТот же маршрут и фокус, мгновенный переходКонтроль пользователя
EЗапись назад/вперёдОдно событие, без второго pushStateВладение историей
FНовый запрос во время ожиданияСтарый результат supersededЗащита callback
{
  "fixtureId": "vt-nav-01",
  "navigationId": "route-17",
  "mode": "view-transition",
  "motionPreference": "no-preference",
  "domCommitted": true,
  "ready": true,
  "outcome": "finished",
  "historyAction": "push",
  "measurementPhase": "ready",
  "browserVersion": "155.0"
}

Сохраняйте один historyAction, один navigationId и не более одной фазы измерения. Не копируйте query-параметры, текст страницы или расшифровки фокуса. Если E создаёт две записи, сначала исправьте роутер; если D меняет фокус, это проблема доступности даже при правильном снимке.

Граница возможностей BotBrowser

BotBrowser может загрузить этот разрешённый сценарий, сохранить маршрут и настройку движения и записать видимые метки цикла. Он не форсирует startViewTransition, не гарантирует время compositor и не сертифицирует объявления помощника. Повтор описывает только выбранные браузер, viewport, профиль и стили.

Источники

Итог: практический вывод

Назначьте одну операцию маршрута, один commit DOM и один конечный результат перехода. Используйте ready для захваченной фазы, finished для очистки и явный abort для несовместимого или ошибочного пути. Сохраняйте историю и фокус при уменьшенном или отсутствующем движении и измеряйте геометрию в одной именованной фазе. BotBrowser повторит такую матрицу, но за опыт отвечают приложение и проверка доступности.

#View Transitions API#Навигация#Доступность#Состояние DOM#Веб-Платформа

Эта статья входит в руководство: Поверхности веб-платформы: карта проверки

Переведите BotBrowser из исследований в продакшн

Используйте эти руководства, чтобы понять модель, а затем перейти к кроссплатформенной валидации, изолированным контекстам и масштабируемому браузерному развертыванию.