Как реализовать переходы между просмотрами в многостраничных приложениях

API View Transition позволяет осуществлять переходы между страницами и изменения пользовательского интерфейса на основе состояния, которые ранее были возможны только с помощью фреймворков JavaScript, в более широком веб-пространстве.

Это включает в себя анимацию перехода между состояниями DOM в одностраничном приложении (SPA) и анимацию навигации между страницами в многостраничном приложении (MPA). Другими словами, это обеспечивает переходы между представлениями на веб-сайте любого типа без громоздких зависимостей от JavaScript и чрезмерной сложности. Это выигрыш для пользователей и разработчиков! Возможно, это изменит правила игры.

В этой статье я сосредоточусь на переходах между представлениями в MPA. Это определено в спецификации модуля CSS View Transitions уровня 2 и называется переходами между представлениями документов. Самое интересное, что основы можно освоить и без JavaScript - достаточно немного декларативного CSS, и вы начнете работать! JavaScript необходим, когда вы хотите реализовать некоторую условную логику.

Кросс-документ вид на переходах теперь поддерживаются в обеих 126 хром и сафари 18.2. Мы можем погружаться в прямо сейчас и используйте просмотр переходов как прогрессивное улучшение уже сегодня! 🠌™

Почему вы должны использовать переходы между видами?

Переходы между видами улучшают навигацию. Более конкретно, они могут:

  • Помогайте пользователям сохранять их текущий контекст, снижая когнитивную нагрузку, привлекая внимание к изменениям и направляя пользователей в их путешествии.
  • Уменьшите предполагаемую задержку загрузки при переключении между состояниями/страницами
  • Анимация, как правило, придает веб-сайту лоск и делает его более привлекательным

Как работает переход от одного вида к другому?

Все переходы между представлениями включают в себя следующие три шага:

  • Браузер делает снимки старого и нового состояния. Старое состояние - это статический снимок экрана, похожий на скриншот. Новое состояние - это отображение документа в реальном времени
  • DOM обновляется без выполнения рендеринга
  • Переход в новое состояние осуществляется с помощью CSS-анимации. По умолчанию используется перекрестный переход. Старый вид анимируется с непрозрачности: 1 до непрозрачности: 0, в то время как новый вид анимируется с непрозрачности: 0 до непрозрачности: 1

Основное различие между переходом к просмотру в SPA и MPA заключается в том, как запускается переход. В MPA переход к просмотру запускается при переходе на другую страницу - это может произойти путем нажатия на ссылку или отправки формы. К навигационным действиям, которые не вызывают перехода к просмотру, относятся переход по строке URL-адреса, щелчок по закладке и перезагрузка страницы.

Если навигация занимает слишком много времени, то переход к просмотру пропускается, что приводит к ошибке. Ограничение для Chrome составляет четыре секунды. Неясно, что определяет начало навигации в этом контексте - нужно ли загружать начальные байты?

Интересно, что на одной странице может быть несколько переходов между представлениями. Мы можем настроить таргетинг на определенные поддеревья DOM для переходов, и их даже можно вложить друг в друга.

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

Создание базового перехода к виду

Чтобы включить переходы между просмотрами веб-сайта, необходимо выполнить два ключевых условия:

  1. Исходная и целевая страницы должны иметь одинаковое происхождение: URL-адреса должны иметь одинаковую схему, домен и порт
  2. Для просмотра переходов должны быть зарегистрированы обе страницы: это делается с помощью CSS-правила @view-transition, как показано ниже:
@view-transition {
  navigation: auto;
}

При этом для страниц должен быть включен режим перекрестного просмотра по умолчанию. Давайте рассмотрим пример.

Вот демонстрация карусели с набором фотографий. Карусель - это компонент, который позволяет просматривать различные материалы, например фотографии в фотогалерее. В этом примере я замедлил анимацию до двух секунд, чтобы подчеркнуть эффект (подробнее об этом позже).:

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

<!-- index.html - first page-->
<h1>Cape Town</h1>
<img src="cape-town.webp" alt=".."/>
<a href="page2.html" class="next"><img src="/1-carousel/shared/img/arrow-right.svg" /></a>

<!-- page2.html - second page-->
<h1>Hong Kong</h1>
<img src="hong-kong.webp" alt=".."/>
<a href="index.html" class="previous"><img src="/1-carousel/shared/img/arrow-left.svg" /></a>
<a href="page3.html" class="next"><img src="/1-carousel/shared/img/arrow-right.svg" /></a> 

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

Overview Of The Page Architecture

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

Мелким шрифтом в спецификации указано, что все эти условия должны быть выполнены:

  • Для просмотра переходов необходимо зарегистрироваться на обеих страницах
  • Исходная и целевая страницы должны иметь одинаковое происхождение
  • Страница должна быть видна на протяжении всего процесса навигации
  • Навигация должна быть инициирована страницей, например, щелчком по ссылке, отправкой формы или сквозной навигацией (назад/вперед).
  • Навигация не должна включать перенаправления с разных источников

Настройка анимации перехода между видами

Мы можем настроить анимацию с помощью псевдоэлементов:

  1. ::view-transition-group() используется для ссылки на конкретный переход вида
  2. ::view-transition-old() используется для ссылки на исходный вид (исходящий переход)
  3. ::view-transition-new() используется для ссылки на целевой вид (входящий переход)

Для каждого из этих псевдоэлементов мы указываем имя перехода в режиме просмотра в качестве аргумента для ссылки на интересующий нас переход в режиме просмотра. Имя перехода в режиме просмотра страницы по умолчанию - root, поскольку оно применяется к элементу :root.

Вы можете изменить продолжительность анимации, разместив на обеих страницах следующее:

::view-transition-group(root) {
  animation-duration: 3s;
}

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

Например, давайте сделаем демонстрацию с анимацией уменьшения размера. Мы уменьшим исходную страницу до такой степени, что она не будет видна, а целевая страница будет видна в расширенном виде:

Мы можем сделать это с помощью следующего CSS:

@keyframes shrink {
  to {
    scale: 0;
  }
}

::view-transition-old(root) {
  animation: shrink 1s;
}

::view-transition-new(root) {
  animation: shrink 1s;
  animation-direction: reverse;
}

Обратите внимание, что мы установили animation-direction: reverse для перехода к целевому виду; это приводит к расширению анимации "Сжатия"!

Выполнение противоположных действий создает симметричный эффект, который может быть приятным. Вам не обязательно это делать - вы можете рассматривать каждую анимацию как отдельный объект. Вы вольны придумывать все, что вам нравится!

Для переходов между документами эти псевдоэлементы доступны только на целевой странице. Не забывайте об этом, если вы выполняете переход между документами только в одном направлении.

Давайте посмотрим, что еще мы можем сделать с нашими переходами между представлениями - на этот раз представим JavaScript!

Настройка переходов между представлениями документов

До сих пор мы демонстрировали, что можем включать переходы между документами и настраивать анимацию с помощью CSS. Это мощно, но если мы хотим добиться большего, нам нужен JavaScript.

API View Transition не удовлетворяет всем нашим потребностям. Существует несколько дополнительных веб-функций, предназначенных для использования в сочетании с ним. Они подразделяются на следующие категории:

  • События жизненного цикла: События pageswap и pagereveal позволяют задавать условные действия для перехода к просмотру. Событие pageswap запускается перед выгрузкой исходной страницы, а событие pagereveal запускается перед отображением целевой страницы
  • Навигационная информация: Браузеры теперь предоставляют объекты NavigationActivation, которые содержат информацию о переходах из одного источника. Это избавляет разработчиков от необходимости самостоятельно отслеживать эту информацию, когда они хотят выполнять разные анимации / действия на основе разных URL-адресов
  • Декларативная блокировка отображения: в некоторых случаях вы можете отложить отображение целевой страницы до тех пор, пока не появится определенный элемент. Это гарантирует, что состояние, в котором выполняется анимация, будет стабильным

Мы обсудим эти особенности далее на некоторых примерах.

Использование событий pageswap и pagereveal

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

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

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

В обоих этих событиях вы можете получить доступ к объекту ViewTransition, используя свойство ViewTransition. Объект ViewTransition представляет собой активный переход вида и предоставляет функциональные возможности для реагирования на переход, достигающий различных состояний, например, когда анимация вот-вот запустится и когда анимация только что закончилась.

Давайте рассмотрим пример, чтобы связать эти концепции воедино.

Демонстрация: Позволяет пользователю отключать / включать переходы между просмотрами

Давайте создадим демонстрационную версию, которая позволит пользователю отключать / включать переходы между видами. Я добавлю флажок в нашу карусель в правом верхнем углу. Если он установлен, мы отключим (пропустим) переходы между видами:

Implementing Option To Skip View Transitions

Нам нужно изменить наш HTML-код, чтобы добавить ввод с помощью флажка, и нам нужно добавить тег script, указывающий на сценарий, который мы собираемся написать. Мы должны добавить тег script в качестве сценария, блокирующего синтаксический анализ, в <head>. Это связано с тем, что событие pagereveal должно выполняться до первой возможности отображения. Это означает, что скрипт не может быть модулем, не может иметь атрибута async и не может иметь атрибута defer:

<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- other elements as before-->

    <!-- our script must be here exactly like this-->
    <script src="script.js"></script>
  </head>

  <body>
    <label>Skip?<input type="checkbox" id="skip" /></label>

    <!-- other elements as before-->
  </body>
</html>

В нашем скрипте мы добавим обработчики событий для событий pageswap и pagereveal. В обработчике событий pageswap мы записываем значение флажка (true или false) в хранилище сеанса, сохраняя его как переменную для пропуска.

Обратите внимание, что я обращаюсь к объекту ViewTransition, чтобы решить, хотим ли мы сохранить значение или нет. Объект ViewTransition имеет значение null, если не происходит перехода вида. Следовательно, эта проверка вернет значение true, когда происходит переход вида.

В обработчике событий pagereveal мы считываем значение переменной skip из хранилища сеанса. Если параметр skip имеет значение "Истина" (хранилище сеанса сохраняет все значения в виде строк), то мы пропускаем переход к просмотру, вызывая функцию ViewTransition.skipTransition():

/* script.js */

// Write to storage on old page
window.addEventListener("pageswap", (event) => {
  if (event.viewTransition) {
    let skipCheckbox = document.querySelector("#skip");
    sessionStorage.setItem("skip", skipCheckbox.checked);
  }
});

// Read from storage on new page
window.addEventListener("pagereveal", (event) => {
  if (event.viewTransition) {
    let skip = sessionStorage.getItem("skip");
    let skipCheckbox = document.querySelector("#skip");

    if (skip === "true") {
      event.viewTransition.skipTransition();
      skipCheckbox.checked = true;
    } else {
      skipCheckbox.checked = false;
    }
  }
});

Мы используем значение из хранилища сеансов в pagereveal, чтобы сохранить состояние флажка на целевой странице. Это поддерживает состояние флажка между переходами по страницам. Помните, что HTTP - это протокол без сохранения состояния; он забудет все о предыдущей странице, если вы ему не скажете!

Если вы не знакомы с хранилищем сеансов, вы можете просмотреть хранилище сеансов в инструментах разработки Chrome. Вы найдете его на вкладке Приложения (как показано на рисунке ниже). На боковой панели под категорией хранилища вы увидите элемент хранилища сеансов. Нажмите на него, и вы увидите источник вашего веб-сайта, например, http://localhost:3000. Нажмите на него, и на экране появятся все сохраненные значения.:

The Application Tab In Chrome DevTools With The Session Storage Item Open That Is Contained Under The Storage Category In The Sidebar

Откроется вкладка "Приложение" в Chrome DevTools с элементом "Хранилище сеансов", который находится в категории "Хранилище" на боковой панели

Информация об активации навигации

В событиях pageswap и pagereveal вы можете выполнять действия, основанные на выполняемой навигации. Эта информация доступна через объект Navigationactivation. Этот объект отображает используемый тип навигации, запись истории навигации по исходной странице и запись истории навигации по целевой странице. Именно с помощью этих записей истории навигации мы можем получить URL-адрес каждой страницы. На момент написания статьи только Chrome поддерживает объект NavigationActivation.

Демонстрация: Карусельная анимация слайдов

Давайте сделаем демонстрацию, чтобы добавить анимацию слайда в нашу карусель. Мы хотим, чтобы произошло следующее:

  • Когда мы нажимаем на следующую ссылку, мы сдвигаем исходный вид влево, а целевой - вправо
  • Когда мы нажимаем на предыдущую ссылку, мы перемещаем исходный вид вправо, а целевой - влево

Для этого сценария можно использовать типы переходов между представлениями. Активному переходу между представлениями можно назначить один или несколько типов с помощью объекта Set, доступного в свойстве ViewTransition.types. В нашем примере при переходе на страницу более высокого уровня в последовательности мы назначаем следующий тип, а при переходе на страницу более низкого уровня мы назначаем предыдущий тип.

На каждый из типов можно ссылаться в CSS для назначения различных анимаций:

Overview Of The Assigning Of Types For Page Navigations. The Link Pointing To A Page Lower In The Sequence Are Assigned A Previous Type, And A Link Pointing To Page Higher In The Sequence Is Assigned A Next Type.

Обзор назначения типов навигации по страницам. Ссылке, указывающей на страницу, расположенную ниже по порядку, присваивается предыдущий тип, а ссылке, указывающей на страницу, расположенную выше по порядку, присваивается следующий тип.

Звучит заманчиво, не так ли? Но как нам определить тип?

Это зависит от вас, чтобы определить тип!

В этом случае я проверю URL исходной и целевой страниц, чтобы определить их порядок. Мы можем получить URL исходной и целевой страниц из объекта NavigationActivation. Он содержит атрибут from, который представляет исходную страницу в виде записи истории, и атрибут entry, который представляет целевую страницу в виде записи истории.

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

  1. index.html
  2. page2.html
  3. page3.html

В нашем коде функция determineTransitionType сравнивает индексы исходной страницы и целевой страницы, чтобы определить, относится ли она к предыдущему типу или к следующему:

window.addEventListener("pageswap", async (e) => {
  if (e.viewTransition) {
    let transitionType = determineTransitionType(
      e.activation.from.url,
      e.activation.entry.url
    );

    e.viewTransition.types.add(transitionType);
  }
});

window.addEventListener("pagereveal", async (e) => {
  if (e.viewTransition) {
    // pagereveal does not expose the NavigationActivation object, we must get it from the global object
    let transitionType = determineTransitionType(
      navigation.activation.from.url,
      navigation.activation.entry.url
    );

    e.viewTransition.types.add(transitionType);
  }
});

function determineTransitionType(sourceURL, targetURL) {
  const sourcePageIndex = getIndex(sourceURL);
  const targetPageIndex = getIndex(targetURL);

  if (sourcePageIndex > targetPageIndex) {
    return "previous";
  } else if (sourcePageIndex < targetPageIndex) {
    return "next";
  }

  return "unknown";
}

function getIndex(url) {
  let index = -1;
  let filename = new URL(url).pathname.split("/").pop();

  if (filename === "index.html") {
    index = 1;
  }

  // extract a number from the filename
  let numberMatches = /\d+/g.exec(filename);
  if (numberMatches && numberMatches.length === 1) {
    index = numberMatches[0];
  }

  return index;
}

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

@keyframes slide-in-from-left {
  from {
    translate: -100vw 0;
  }
}

@keyframes slide-in-from-right {
  from {
    translate: 100vw 0;
  }
}

@keyframes slide-out-to-left {
  to {
    translate: -100vw 0;
  }
}

@keyframes slide-out-to-right {
  to {
    translate: 100vw 0;
  }
}

Мы связываем анимацию с типом перехода вида с псевдоклассом :active-view-transition-type() и указываем тип в качестве аргумента.

Для каждого типа мы задаем анимацию для исходной страницы с помощью ::view-transition-old() и целевой страницы с помощью ::view-transition-new():

::view-transition-group(root) {
  animation-duration: 400ms;
}

html:active-view-transition-type(next) {
  &::view-transition-old(root) {
    animation-name: slide-out-to-left;
  }

  &::view-transition-new(root) {
    animation-name: slide-in-from-right;
  }
}

html:active-view-transition-type(previous) {
  &::view-transition-old(root) {
    animation-name: slide-out-to-right;
  }

  &::view-transition-new(root) {
    animation-name: slide-in-from-left;
  }
}

Требуется некоторое время, чтобы сориентироваться во всем этом! Но когда вы привыкнете, вы сможете создавать разнообразные анимации для перехода между документами. Это захватывающая перспектива!

Декларативная блокировка рендеринга

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

<link rel="expect" blocking="render" href="#sidebar">

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

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

Поддержка браузером API перехода к просмотру

Браузер активно поддерживает переходы между режимами просмотра, причем как Chrome, так и Safari охватывают большинство задействованных API:

Соображения, связанные с доступностью API перехода к представлению

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

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

/* Enable view transitions for everyone except those who prefer reduced motion */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
}

Рекомендации по вашей среде разработки

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

Я обнаружил, что самый простой способ убедиться, что кэширование не происходит, - это открыть инструменты разработки и установить флажок Отключить кэширование на вкладке Сеть:

The Network Tab Of Chome DevTools With The Disable Cache Checkbox Enabled

Кроме того, если вы привыкли выполнять отладку с помощью console.log() или аналогичного метода, это неэффективно при работе на двух страницах. При каждой навигации журнал консоли будет очищаться. Лучше использовать sessionStorage для ведения журнала, если это ваш предпочтительный метод отладки.

Демонстрации

Все демонстрации, которые я рассмотрел в этой статье, можно найти в этом репозитории на GitHub. Я также включил некоторые из демонстраций, подготовленных командой разработчиков Chrome.

Вот ссылки на живые страницы упомянутых демонстраций:

  • Базовая карусель
  • Карусель с анимацией сжатия
  • Карусель с флажком пропускать переход к просмотру
  • Карусель с анимацией слайдов (только для Chrome)

Переход между страницами должен осуществляться в течение четырех секунд, чтобы увидеть переходы между видами.

Вывод

Возможность добавлять переходы между страницами и изменения пользовательского интерфейса на основе состояния на любом веб-сайте - это значительный шаг вперед. Возможность применять переходы между представлениями без громоздких зависимостей от JavaScript и головокружительной сложности фреймворков полезна для пользователей и разработчиков! Вы можете начать работать с простым CSS. Если вам нужна условная логика для перехода к представлению, то вам нужно написать некоторый JavaScript-код.

В целом, я был впечатлен этой возможностью. Однако должен признать, что мне было трудно понять аспекты использования переходов между представлениями документов. Из прочитанных мною объяснений не была очевидна взаимосвязь между API перехода к представлению и сопутствующими API, такими как NavigationActivation. Как только вы преодолеете эти трудности с пониманием, вы сможете создавать эффективные переходы к представлению с помощью кода JavaScript умеренной длины.

Браузеры активно поддерживают API-интерфейсы, связанные с переходами между режимами просмотра, причем большинство из них доступны как в Chrome, так и в Safari. В любом случае, вы можете использовать переходы между режимами просмотра в качестве прогрессивного улучшения. Помните, что это новая веб-функция, поэтому вы можете столкнуться с некоторыми проблемами.

Также важно понимать, что переходы между страницами требуют быстрой загрузки страниц. Если переход занимает более четырех секунд, Chrome отключит переход. Дополнительная веб-функция, которую вы можете использовать для ускорения навигации, - это предварительная визуализация. API-интерфейс Speculation Rules разработан для повышения производительности будущих навигаций. Эти функции указывают на более быструю и функциональную работу в Интернете, но для того, чтобы реализовать преимущества, потребуется, чтобы люди создавали веб-сайты по-новому.

Возможности переходов между видами также расширяются. Недавно были добавлены вложенные переходы между видами, и в настоящее время изучаются некоторые экспериментальные дополнения. Команда разработчиков Chrome заявила, что они хотят добавить больше возможностей для условий навигации, возможно, даже разрешить переходы между исходными видами!

Попробуйте просмотреть переходы между документами!

Рекомендации

  • Спецификация модуля CSS View Transitions 2-го уровня
  • Просмотр описания API переходов, CG веб-инкубатора

Â'Â