Долгое время плавные анимированные переходы между страницами были привилегией SPA: чтобы получить эффект, когда один экран «перетекает» в другой, приходилось тащить в проект React, роутер и какую-нибудь библиотеку вроде Framer Motion. Для классических многостраничных сайтов на Laravel, WordPress или просто на статике это было недоступно — браузер при навигации просто перерисовывал документ с нуля, и любая анимация обрывалась.
С View Transitions API ситуация изменилась. Теперь браузер умеет анимировать переход между двумя разными документами нативно, без единой строчки JavaScript. В этой статье разберём, как подключить кросс-документные переходы на обычном MPA, как настроить анимацию через CSS и как при необходимости управлять процессом из JS.
Что такое View Transitions API и где он работает
View Transitions API делает «снимок» текущего состояния страницы, затем подготавливает новое состояние и анимирует переход между ними. У API два режима:
- Same-document — переходы внутри одного документа (актуально для SPA), запускаются через
document.startViewTransition(). - Cross-document — переходы между разными страницами при обычной навигации (то, что нужно MPA), включаются декларативно через CSS.
Поддержка на середину 2026 года: кросс-документные переходы работают в браузерах на движке Chromium начиная с Chrome 126 и в Safari начиная с версии 18.2. В Firefox функциональность пока скрыта за флагом, поэтому относиться к ней стоит как к прогрессивному улучшению — там, где API не поддержан, пользователь просто увидит обычную мгновенную навигацию без ошибок.
Включаем переходы между страницами
Чтобы браузер начал анимировать переходы, обе страницы — та, с которой уходим, и та, на которую приходим, — должны явно согласиться на это через CSS-правило @view-transition:
@view-transition {
navigation: auto;
}
Это буквально всё, что нужно для базового эффекта. Если правило присутствует на обеих страницах одного источника (same-origin), при навигации старое содержимое плавно растворится, а новое проявится — стандартная кросс-фейд-анимация. Значение navigation: none (по умолчанию) переходы отключает, navigation: auto — включает для срабатывающих по навигации переходов.
Важный нюанс: переходы работают только между страницами одного origin и при «обычной» навигации (клик по ссылке, отправка формы, кнопки вперёд/назад). Это идеально ложится на server-rendered проекты, где страницы и так отдаются сервером.
Настраиваем анимацию через псевдоэлементы
Во время перехода браузер строит дерево псевдоэлементов, которыми можно управлять из CSS. Корневой снимок доступен через группу с именем root, а сами кадры — через ::view-transition-old() (старое состояние) и ::view-transition-new() (новое):
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.4s;
animation-timing-function: ease-in-out;
}
Можно полностью переопределить анимацию своими @keyframes. Например, сделать «выезд» новой страницы снизу вместо кросс-фейда:
@keyframes slide-from-bottom {
from { transform: translateY(30px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
@keyframes fade-out {
to { opacity: 0; }
}
::view-transition-old(root) {
animation: 0.3s ease-out both fade-out;
}
::view-transition-new(root) {
animation: 0.4s ease-out both slide-from-bottom;
}
Полная иерархия псевдоэлементов выглядит так: ::view-transition — корневой оверлей поверх страницы, внутри него для каждого именованного элемента создаётся ::view-transition-group(), в ней — ::view-transition-image-pair(), а уже внутри неё лежат ::view-transition-old() и ::view-transition-new().
Именованные переходы для отдельных элементов
Самое интересное начинается, когда нужно анимировать не всю страницу целиком, а конкретный элемент — например, чтобы превью статьи в списке плавно «вырастало» в полноразмерную обложку на странице самой статьи. Для этого элементам на обеих страницах присваивают одинаковое имя через свойство view-transition-name:
/* На странице списка статей */
.card__cover {
view-transition-name: article-cover;
}
/* На странице самой статьи */
.article__hero {
view-transition-name: article-cover;
}
Браузер увидит, что элемент с именем article-cover существует и до, и после навигации, и анимирует его положение и размер от одного состояния к другому — получится эффект «morph». Ключевое правило: каждое имя должно быть уникальным в пределах одного документа, иначе переход для этого элемента сорвётся.
Если элементов много (например, карточки в ленте), удобно генерировать имена автоматически. В современных браузерах для этого есть значение view-transition-name: auto, которое присваивает элементу стабильное имя на основе его идентичности. При желании можно задавать имена и через nth-child или из шаблона на стороне сервера.
Управление переходом из JavaScript
Декларативного подхода хватает в большинстве случаев, но иногда нужно вмешаться: пропустить анимацию при определённых условиях, выбрать разный эффект в зависимости от направления навигации или почистить ресурсы. Для кросс-документных переходов есть два события.
pageswap срабатывает на уходящей странице непосредственно перед навигацией. Здесь можно, например, динамически назначить view-transition-name именно тому элементу, по которому кликнул пользователь:
window.addEventListener('pageswap', (event) => {
if (!event.viewTransition) return;
const target = event.activation?.entry?.url;
// например, отключаем переход для определённых маршрутов
if (target && new URL(target).pathname === '/logout') {
event.viewTransition.skipTransition();
}
});
pagereveal срабатывает на новой странице перед её первой отрисовкой. Это подходящее место, чтобы подготовить целевые элементы или выбрать тип анимации:
window.addEventListener('pagereveal', (event) => {
if (!event.viewTransition) return;
const fromUrl = navigation.activation?.from?.url;
if (fromUrl && new URL(fromUrl).pathname.startsWith('/blog')) {
document.documentElement.dataset.transition = 'slide';
}
});
Объект event.viewTransition — это тот же ViewTransition, что и в same-document API: у него есть промисы ready, finished и updateCallbackDone, а также метод skipTransition() для мгновенного завершения.
Прогрессивное улучшение и доступность
View Transitions API спроектирован как чистое улучшение: если браузер его не поддерживает или правило @view-transition не сработало, пользователь получит обычную навигацию. Поэтому добавлять его в продакшен можно без оглядки на старые браузеры.
Единственное, о чём стоит помнить, — пользователи, которые отключили анимации в системе. Уважайте их настройку через медиазапрос prefers-reduced-motion:
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
Так контент по-прежнему будет обновляться корректно, но без движения, способного вызвать дискомфорт.
Итоги
Кросс-документные View Transitions — это редкий случай, когда новая платформенная возможность реально упрощает код, а не добавляет сложности. Чтобы получить базовый плавный переход между страницами обычного MPA, достаточно одного CSS-правила @view-transition { navigation: auto; } на каждой странице. Дальше эффект тонко настраивается через псевдоэлементы и @keyframes, а именованные переходы с view-transition-name дают «дорогие» morph-эффекты между списком и детальной страницей без единой зависимости.
Для server-rendered проектов на Laravel, WordPress или статике это означает, что плавность интерфейса, ради которой раньше переходили на SPA, теперь доступна бесплатно и нативно. Начните с одной строки CSS, проверьте в Chrome и Safari, и постепенно добавляйте именованные переходы там, где они действительно усиливают навигацию.