javascript

View Transitions API: полное руководство по плавным переходам в браузере

  • воскресенье, 15 февраля 2026 г. в 00:00:06
https://habr.com/ru/articles/996498/

Помните 2015 год? Тогда Пол Льюис из Google представил концепцию FLIP (First, Last, Invert, Play) — революционный на тот момент способ делать анимации интерфейса со стабильными 60 fps. Идея была гениальной: вместо того, чтобы анимировать свойства разметки (width, top), мы измеряем начальное и конечное состояние элементов, а затем анимируем только transform.

В теории звучало отлично, но на практике реализация была трудоёмкой: требовались вычисления координат getBoundingClientRect, ручной контрольrequestAnimationFrame и бесконечные вычисления таймингов. В итоге большинство разработчиков просто отказывались от сложных переходов, потому что цена поддержки такого кода была слишком высока.

Прошло восемь лет, и в Chrome 111 появился View Transitions API. По сути, браузер наконец-то научился делать FLIP за нас. API решает те же задачи, что FLIP, но принципиально другим способом. Джейк Арчибальд на Chrome Dev Summit показал, как три строчки нативного кода заменяют сотни строк самописных скриптов.

Давайте разберёмся, как это работает, где подводные камни и можно ли использовать в рабочей среде прямо сейчас?

Зачем нам ещё один API?

Если вы когда-либо пользовались веб-приложениями на iOS или Android, вы видели этот эффект: нажимаете на карточку товара, и картинка плавно «перелетает» на страницу деталей, превращаясь в заголовок. В вебе повторить такое было мучительно сложно.

Вы можете сказать: «У нас же есть CSS Transitions и Web Animations API (WAAPI), зачем ещё один?» Разница фундаментальна: старые методы анимируют элементы, а View Transitions API анимирует состояния.

Вот наглядное сравнение:

Задача

CSS Transitions

Web Animations API

View Transitions API

Анимация layout-свойств

Дёргается

Дёргается

Плавно*

Shared elements

Невозможно

Сложно

Из коробки

Кросс-документные переходы

Нет

Нет

Да

Синхронизация состояний

Вручную

Вручную

Автоматически

*Плавно, так как анимируются снимки, а не layout

Чтобы эффективно использовать API, нужно понимать, как браузер организует переход изнутри.

Ключевые концепции: снимки, псевдоэлементы и жизненный цикл

Когда вы вызываете startViewTransition(), браузер проходит чёткую последовательность: захватывает снимки элементов с view-transition-name, выполняет ваш callback с изменениями DOM, захватывает снимки нового состояния и создаёт дерево псевдоэлементов:

::view-transition
::view-transition-group(name)
 		 ::view-transition-image-pair(name)
 ::view-transition-old(name)   /* снимок ДО */
     			 ::view-transition-new(name)   /* снимок ПОСЛЕ */

Поскольку мы манипулируем «картинками» (снимками), а не живыми DOM-узлами, анимация, как правило, выполняется на уровне композитора. Отсюда и стабильная производительность.

Быстрый старт: минимальный рабочий пример

Давайте посмотрим на код, как внедрить View Transitions в существующий проект. Это на самом деле проще, чем кажется.

Представьте типичную ситуацию в SPA: у вас есть функция, которая получает новый контент и просто заменяет старый HTML в контейнере. Обычно это происходит мгновенно и резко.

Чтобы добавить плавную анимацию, нам не нужно переписывать логику рендеринга. Нам нужно всего лишь «обернуть» момент изменения DOM в вызов document.startViewTransition.

Вот универсальный паттерн, который (важно!) не сломает сайт в большинстве случаев при простом DOM-обновлении:

function updateContent(newHTML) {
  const container = document.querySelector('#container');

// 1. Проверяем поддержку (Progressive Enhancement)

  // Если браузер не знает про API, просто обновляем контент по-старому:

  if (!document.startViewTransition) {
    container.innerHTML = newHTML;
    return;
  }

  // 2. Если поддержка есть — запускаем магию:

  document.startViewTransition(() => {

    // Внутри этого колбэка мы делаем всё то же самое:

    // просто меняем DOM. Браузер сам поймёт, что изменилось.

container.innerHTML = newHTML;
  });
}

Что здесь происходит? Мы говорим браузеру: «Запиши, как всё выглядит сейчас. Потом выполни мою функцию updateContent. А затем плавно преврати старую картинку в новую».

По ссылке на демо доступен готовый рабочий пример View Transitions API, который включает HTML-структуру, CSS-оформление, JavaScript-логику:

Управление процессом через промисы

startViewTransition возвращает объект с промисами, которые позволяют тонко контролировать жизненный цикл перехода. Это полезно, если вам нужно скоординировать JS-анимацию с переходом.

JavaScript

const transition = document.startViewTransition(() => updateDOM());

// 1. DOM обновлён, но анимация ещё не началась:

await transition.updateCallbackDone;

// 2. Псевдоэлементы созданы, анимация готова к старту:

await transition.ready;

// 3. Анимация полностью завершена, псевдоэлементы удалены:

await transition.finished;

Кастомизация анимаций через CSS

По умолчанию браузер делает простое перекрёстное затухание (cross-fade). Чтобы сделать красиво, нам понадобится CSS и свойство view-transition-name.

1. Связывание элементов.

Чтобы анимировать конкретный блок отдельно от остальной страницы, дайте ему уникальное имя:

CSS

card {
  view-transition-name: card-hero;
}

2. Настройка анимации.

Теперь мы можем обратиться к псевдоэлементам этого блока и задать свои кейфреймы:

CSS

/* Старое состояние улетает */

::view-transition-old(card-hero) {
  animation: 300ms ease-out both fade-scale-out;
}

/* Новое состояние прилетает */

::view-transition-new(card-hero) {
  animation: 300ms ease-in both fade-scale-in;
}

@keyframes fade-scale-out {
  to { opacity: 0; transform: scale(0.9); }
}

@keyframes fade-scale-in {
  from { opacity: 0; transform: scale(1.1); }
}

По ссылке доступен код примера, где карточка меняет текст с кастомной анимацией масштабирования и прозрачности:

Shared elements: подходы и опасные ловушки

Это та самая киллер-фича: если на одной странице у картинки view-transition-name: hero, и на новой странице у (другой!) картинки тоже view-transition-name: hero, то браузер автоматически рассчитает их позиции и размеры, плавно трансформируя первую во вторую.

И вот тут главная ловушка: view-transition-name должен быть уникальным на странице в каждый момент времени. Если вы зададите всем карточкам в списке имя hero, то переход просто сломается.

Вот какое решение у этой ситуации может быть: назначайте имя динамически перед кликом.

JavaScript

function openProduct(id) {
  const cardImg = document.querySelector(`[data-id="${id}"] img`);

 // 1. Временно даём имя кликнутому элементу:

  cardImg.style.viewTransitionName = 'hero';
  
  const transition = document.startViewTransition(() => {
    renderProductPage(id);

  // На новой странице у большой картинки в CSS уже должно быть прописано view-transition-name: hero

});

  // 2. Убираем имя после завершения, чтобы не было конфликтов:

  transition.finished.then(() => {
    cardImg.style.viewTransitionName = '';
  });
}

Продвинутые сценарии

Асинхронные обновления и Suspense

View Transition API ожидает, что callback выполнится синхронно или вернёт промис. Браузер будет держать «замороженный» экран до завершения этого промиса.

JavaScript

document.startViewTransition(async () => {
  const data = await fetchData(); // Ждём сеть
  renderContent(data);            // Обновляем DOM
});

Небольшой совет: не затягивайте ожидание, иначе пользователь подумает, что интерфейс завис.

Работа с фреймворками (React)

React обновляет DOM асинхронно и пакетами (batching). Чтобы View Transitions API сработал корректно, обновление DOM должно произойти внутри колбэка.

В React для этого используем flushSync, который заставляет React применить изменения немедленно:

JavaScript

import { flushSync } from 'react-dom';
document.startViewTransition(() => {
  flushSync(() => {
    setState(newState);
  });
});

Note: В экспериментальных сборках React ведётся работа над нативной интеграцией, но в стабильный релиз этот API пока не вошёл. Поэтому сейчас рабочим решением для продакшена остаётся ручной вызов нативного document.startViewTransition в связке с flushSync.

Cross-document (MPA) переходы

С версии Chrome 126+ (и Safari 18+) можно анимировать переходы между разными HTML-страницами. Больше не нужно делать SPA, чтобы получить плавные переходы!

Достаточно добавить CSS на обеих страницах:

CSS

@view-transition {
  navigation: auto;
}

Для мгновенной загрузки следующей страницы используйте Speculation Rules API (prerender):

HTML

<script type="speculationrules">
{ "prerender": [{ "source": "document", "where": { "selector": "a" } }] }
</script>

Чеклист перед выходом в продакшен

API мощный, но при неправильном использовании легко сломать вёрстку или доступность. Проверьте себя перед релизом:

  • Обязательно проверяйте наличие document.startViewTransition.

  • Уважайте пользователей, у которых отключена анимация. Вот пример кода, который делает переходы мгновенными для тех, кто выбрал системную настройку prefers-reduced-motion:

CSS

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation-duration: 0.01ms !important;
    }
}
  • Ориентируйтесь примерно на эту производительность:

    Длительность анимации не должна превышать 300-400 мс (это влияет на метрику INP).

    Обязательно должен быть протестирован LCP на мобильных устройствах.

  • Убедитесь, что нет дублей view-transition-name.

  • Проверьте отсутствие сдвигов контента и при проблемах с изображениями используйте object-fit: cover.

Итог

View Transitions API — это тот редкий случай, когда веб-стандарты догнали веб-приложения. Мы получили инструмент, который берёт на себя самую сложную математику анимаций (синхронизацию, позиционирование, композитинг) и оставляет нам чистое творчество.

Статус поддержки:

  • Chrome/Edge — полная поддержка (v111+).

  • Safari — поддержка появляется в Safari 18, на момент написания частично и с ограничениями.

  • Firefox — в активной разработке. API уже доступен в экспериментальных сборках (Nightly), но в стабильный релиз пока не вошёл.

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