View Transitions API: полное руководство по плавным переходам в браузере
- воскресенье, 15 февраля 2026 г. в 00:00:06

Помните 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 показал, как три строчки нативного кода заменяют сотни строк самописных скриптов.
Давайте разберёмся, как это работает, где подводные камни и можно ли использовать в рабочей среде прямо сейчас?
Если вы когда-либо пользовались веб-приложениями на 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;
По умолчанию браузер делает простое перекрёстное затухание (cross-fade). Чтобы сделать красиво, нам понадобится CSS и свойство view-transition-name.
Чтобы анимировать конкретный блок отдельно от остальной страницы, дайте ему уникальное имя:
CSS
card { view-transition-name: card-hero; }
Теперь мы можем обратиться к псевдоэлементам этого блока и задать свои кейфреймы:
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); } }
По ссылке доступен код примера, где карточка меняет текст с кастомной анимацией масштабирования и прозрачности:

Это та самая киллер-фича: если на одной странице у картинки 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 = ''; }); }
View Transition API ожидает, что callback выполнится синхронно или вернёт промис. Браузер будет держать «замороженный» экран до завершения этого промиса.
JavaScript
document.startViewTransition(async () => { const data = await fetchData(); // Ждём сеть renderContent(data); // Обновляем DOM });
Небольшой совет: не затягивайте ожидание, иначе пользователь подумает, что интерфейс завис.
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.
С версии 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), но в стабильный релиз пока не вошёл.
С правильным фолбэком эту технологию можно и нужно использовать уже сегодня, чтобы делать веб чуть более приятным местом.