Введение в View Transitions API

Источник: «An Introduction to the View Transitions API»
Новый View Transitions API предлагает более простой способ анимации между двумя состояниями DOM — даже между загрузками страницы. Это прогрессивное усовершенствование, работающее уже сегодня.

CSS-переходы и анимация произвели революцию в веб-эффектах за последнее десятилетие, но не все так просто. Рассмотрим список элементов — например, десять изображений с заголовками, — который мы хотим перевести в новый список элементов с помощью эффекта cross-fade. Текущий подход:

  1. Сохранить старые элементы DOM
  2. Создать новые DOM-элементы, добавить их на страницу, обеспечив соответствующее расположение
  3. Затухание старого набора при появлении нового набора, затем
  4. (опционально) Заменить старые элементы DOM на новые

До сих пор не было возможности просто обновить DOM — до сих пор! View Transitions API использует следующий процесс:

  1. API делает снимок текущего состояния страницы.
  2. При необходимости мы обновляем DOM, добавляя или удаляя элементы.
  3. API делает снимок нового состояния страницы.
  4. API выполняет анимацию между двумя состояниями, используя стандартное затухание или любые CSS-анимации, которые мы определим.

Нам нужно только обновить DOM, как мы это уже делаем. Несколько строк дополнительного кода могут постепенно улучшать страницу при наличии View Transitions API для создания эффектов, похожих на презентацию.

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

View Transition API для навигации также доступен в Chrome 115+ и предлагает анимацию между загрузками отдельных страниц — как, например, на типичных сайтах WordPress. Это ещё проще в использовании и не требует использования JavaScript.

Mozilla и Apple не раскрыли своих намерений по внедрению API в Firefox и Safari. Любой браузер без View Transitions API будет продолжать работать, поэтому добавлять эффекты можно уже сегодня.

Новые старые техники

Разработчики определённого возраста, возможно, испытывают дежавю. Компания Microsoft добавила переходы между элементами и всей страницей в Internet Explorer 4.0 (выпущен в 1997 г.) с последующими обновлениями в IE5.5 (выпущен в 2000 г.). С помощью тега <meta> мы могли добавлять вдохновлённые PowerPoint боксы, круги, салфетки, растворения, жалюзи, слайды, полосы и спирали:

<meta http-equiv="Page-Enter" content="progid:DXImageTransform.Microsoft.Iris(Motion='in', IrisStyle='circle')">
<meta http-equiv="Page-Exit" content="progid:DXImageTransform.Microsoft.Iris(Motion='out', IrisStyle='circle')">

Странно, но эта технология так и не получила широкого распространения. Это не было веб-стандартом, но W3C находился в зачаточном состоянии, и разработчики с удовольствием использовали множество других технологий, специфичных для IE!

Почему альтернатива появилась только через четверть века?!

Создание внутристраничных переходов

Просмотрите следующий пример CodePen в Chrome и кликните на навигации в заголовке, чтобы увидеть односекундное перетекание между двумя состояниями (picsum.photos не доступен в РФ).

See the Pen

HTML-страница содержит два элемента <article> с id article1 и article2 для блоков контента:

<main><div id="articleroot">

<article id="article1">

<h2>Article 1 content</h2>

<figure>
<img src="image1.jpg" width="800" height="500" alt="image" />
</figure>

<p>Lorem ipsum dolor sit amet...</p>

</article>

<article id="article2">

<h2>Article 2 content</h2>

<figure>
<img src="image2.jpg" width="800" height="500" alt="image" />
</figure>

<p>Ut pretium ac orci nec dictum...</p>

</article>

</div></main>

Функция switchArticle() обрабатывает все обновления DOM. Она показывает или скрывает каждую статью, добавляя или удаляя атрибут hidden. При загрузке страницы активная статья определяется по location.hash URL страницы или, если он не задан, по первому элементу <article>:

// получить все статьи на странице
const article = document.getElementsByTagName('article');

// показать одну статью при загрузке страницы
switchArticle();

// показать активную статью
function switchArticle(e) {

const hash = e?.target?.hash?.slice(1) || location?.hash?.slice(1);

Array.from(article).forEach((a, i) => {

if (a.id === hash || (!hash && !i)) {
a.removeAttribute('hidden');
}
else {
a.setAttribute('hidden', '');
}

});

}

Функция-обработчик событий отслеживает все клики на странице и вызывает switchArticle(), когда пользователь нажимает на ссылку с #hash:

// событие клика навигации
document.body.addEventListener('click', e => {

if (!e?.target?.hash) return;
switchArticle(e);

});

Теперь мы можем обновить этот обработчик для использования View Transitions, передав функцию switchArticle() в качестве обратного вызова в document.startViewTransition() (предварительно проверив доступность API):

document.body.addEventListener('click', e => {

if (!e?.target?.hash) return;

if (document.startViewTransition) {

// используем эффект View Transition
document.startViewTransition(() => switchArticle(e));

}
else {

// View Transition недоступен
switchArticle(e);
}

});

document.startViewTransition() делает снимок/snapshot начального состояния, запускает switchArticle(), делает новый снимок/snapshot нового состояния и создаёт стандартное полусекундное затухание между ними.

В CSS имеются следующие селекторы для выбора старого и нового состояния:

::view-transition-old(root) {
/* animate out effects */
}

::view-transition-new(root) {
/* animate in effects */
}

В приведённом примере длительность анимации увеличена до одной секунды, поэтому эффект затухания/fade более заметен:

::view-transition-old(root),
::view-transition-new(root)
{
animation-duration: 1s;
}

view-transition-group(root) может применять эффекты одновременно к старому и новому состоянию, хотя в большинстве случаев мы вряд ли будем применять одну и ту же анимацию.

Асинхронное обновление DOM

Обратный вызов, передаваемый в document.startViewTransition(), может возвращать Promise, что позволяет выполнять асинхронные обновления. Например:

document.startViewTransition(async () => {

const response = await fetch('/some-data');
const json = await response.json();
doDOMUpdates(json);
await sendAnalyticsEvent();

});

При этом страница замораживается до тех пор, пока Promise не будет выполнен, поэтому задержки могут повлиять на работу пользователя. Более эффективно выполнять как можно больше кода за пределами вызова .startViewTransition(). Например:

const response = await fetch('/some-data');
const json = await response.json();

document.startViewTransition(() => doDOMUpdates(json));

await sendAnalyticsEvent();

Создание более сложных переходов

Следующая демонстрация CodePen добавляет более красивую анимацию с помощью селекторов ::view-transition-old(root) и ::view-transition-new(root).

See the Pen

В CSS определены анимации transition-out и transition-in с затуханием и вращением:

::view-transition-old(root) {
animation: 1s transition-out 0s ease;
}

::view-transition-new(root) {
animation: 1s transition-in 0s ease;
}

@keyframes transition-out {
from {
opacity: 1;
translate: 0;
rotate: 0;
}
to {
opacity: 0;
translate: -3rem -5rem;
rotate: -10deg;
}
}

@keyframes transition-in {
from {
opacity: 0;
translate: 3rem 5rem;
rotate: -10deg;
}
to {
opacity: 1;
translate: 0;
rotate: 0;
}
}

Анимация применяется ко всей странице, включая элемент <header>, который выглядит несколько странно. Мы можем применять анимацию (или не применять её) к отдельным элементам, задавая имя перехода вида (view-transition-name):

header {
view-transition-name: header;
}

Теперь мы можем нацелиться на этот элемент и применить другую анимацию:

::view-transition-old(header) {
}

::view-transition-new(header) {
}

В данном случае мы не хотим, чтобы заголовок имел какие-либо эффекты, поэтому нет необходимости задавать анимацию. Селекторы ::view-transition-old(root) и ::view-transition-new(root) теперь применяются ко всем элементам, кроме <header>. Он остаётся на месте.

See the Pen

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

Использование Web Animations API

Хотя для большинства эффектов достаточно CSS, Web Animations API позволяет дополнительно управлять временем и эффектами на JavaScript.

document.startViewTransition() возвращает объект, выполняющий promise .ready, который решает, когда будут доступны старый и новый псевдоэлементы перехода (обратите внимание на свойство pseudoElement во втором параметре .animate()):

// использование Web Animations API
const transition = document.startViewTransition( doDOMupdate );

transition.ready.then( () => {

document.documentElement.animate(
[
{ rotate: '0deg' },
{ rotate: '360deg' },
],
{
duration: 1000,
easing: 'ease',
pseudoElement: '::view-transition-new(root)',
}
);

});

Создание многостраничных навигационных переходов

Мы также можем использовать View Transitions при переходе пользователя между загрузками страниц в многостраничных приложениях (MPA), таких, как типичные сайты WordPress. Это известно как viewTransition API for navigations, который мы должны включить в chrome://flags/ в Chrome 115 (в настоящее время это ночная сборка Canary для разработчиков). Флаг доступен и в предыдущих версиях браузера, но API может отсутствовать или быть нестабильным.

Этот процесс проще, чем внутристраничные переходы, поскольку он осуществляется с помощью одного метатега в HTML <head>:

<meta name="view-transition" content="same-origin" />

Затем мы можем определить CSS-селекторы ::view-transition-old и ::view-transition-new идентичным образом, как показано выше. Нам не требуется никакого JavaScript, если мы не хотим использовать Web Animations API.

Навигационный API может быть включён или не включён по умолчанию после выхода финальной версии Chrome 115. Мы можем использовать эту технику уже сегодня, поскольку браузеры, не поддерживающие API, будут возвращаться к стандартной, не анимированной загрузке страниц.

Отключение анимации

Анимация может вызывать дискомфорт у некоторых людей, страдающих двигательными расстройствами. В большинстве операционных систем предусмотрена возможность отключения эффектов. Мы можем определить это с помощью медиа-запроса CSS prefers-reduced-motion и отключить анимацию соответствующим образом:

@media (prefers-reduced-motion) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*)
{
animation: none !important;
}
}

Итоги

View Transitions API упрощает анимацию при изменении состояния элементов на странице и между её загрузками. Подобные переходы были возможны и раньше, но они требовали значительного объёма JavaScript, и нужно было быть осторожным, чтобы не нарушить навигацию браузера, например, кнопку "Назад".

API является новым. Нет никаких гарантий, что он останется неизменным, станет стандартом W3C или будет реализован в Firefox и Safari. Однако мы можем использовать API уже сегодня, поскольку он является прогрессивным усовершенствованием. Наши приложения будут продолжать работать в браузерах, не поддерживающих API; они просто не будут показывать анимацию. Есть риск, что API изменится, но даже если нам придётся осуществлять поддержку, наш старый код не сломает сайт, а обновления, скорее всего, будут минимальными.

Недостатки? API может привести к появлению в Сети раздражающе длинных и диких анимаций, поскольку владельцы сайтов считают их фирменными! В идеале анимация должна быть быстрой и ненавязчивой, чтобы подчеркнуть изменения в пользовательском интерфейсе. Меньше — значит больше.

Дополнительные ссылки:

Дополнительные материалы

Предыдущая Статья

Использование Bun в качестве менеджера пакетов в PHP проектах

Следующая Статья

Поддержка Bun в Laravel Sail и Forge