Когда переход представления происходит между двумя разными документами, это называется переходом представления между документами . Обычно это происходит в многостраничных приложениях (MPA). Переходы между представлениями между документами поддерживаются в Chrome начиная с Chrome 126.
Переходы между представлениями между документами основаны на тех же строительных блоках и принципах, что и переходы между представлениями одного документа , что сделано намеренно:
- Браузер делает снимки элементов, которые имеют уникальное
view-transition-name
как на старой, так и на новой странице. - DOM обновляется, пока рендеринг подавляется.
- И, наконец, переходы поддерживаются CSS-анимацией.
Отличие от переходов представлений одного и того же документа заключается в том, что при переходах представлений между документами вам не нужно вызывать document.startViewTransition
, чтобы начать переход представлений. Вместо этого триггером для перехода между представлениями документов является навигация с одной страницы на другую из одного и того же источника — действие, которое обычно выполняется пользователем вашего веб-сайта, щелкающим ссылку.
Другими словами, не существует API, который можно было бы вызвать, чтобы начать переход между двумя документами. Однако есть два условия, которые необходимо выполнить:
- Оба документа должны существовать в одном и том же источнике.
- Обе страницы должны дать согласие, чтобы разрешить переход вида.
Оба этих условия объяснены далее в этом документе.
Переходы между представлениями между документами ограничены навигацией по одному и тому же источнику.
Переходы между представлениями между документами ограничены только навигацией по одному и тому же источнику . Навигация считается одинаковой, если происхождение обеих участвующих страниц одинаково.
Происхождение страницы — это комбинация используемой схемы, имени хоста и порта, как подробно описано на web.dev .
![Пример URL-адреса с выделенной схемой, именем хоста и портом. В совокупности они образуют начало.](https://developer.chrome.com/static/docs/web-platform/view-transitions/image/origin.png?authuser=5&hl=ru)
Например, при переходе от developer.chrome.com
к developer.chrome.com/blog
можно выполнить переход между документами, поскольку они имеют одинаковое происхождение. Этот переход невозможен при переходе с developer.chrome.com
на www.chrome.com
, поскольку они имеют перекрестное происхождение и один и тот же сайт.
Переходы между представлениями между документами являются добровольными.
Чтобы иметь возможность перехода между двумя документами, обе участвующие страницы должны разрешить это. Это делается с помощью at-правила @view-transition
в CSS.
В правиле @view-transition
установите для дескриптора navigation
значение auto
, чтобы включить переходы между представлениями для навигации между документами с одним и тем же источником.
@view-transition {
navigation: auto;
}
Установив для дескриптора navigation
значение auto
вы разрешаете переходы между представлениями для следующих типов NavigationType :
-
traverse
-
push
илиreplace
, если активация не была инициирована пользователем через механизмы пользовательского интерфейса браузера.
К навигациям, исключенным из auto
, относятся, например, навигация с использованием строки URL-адреса или щелчка по закладке, а также любая форма перезагрузки, инициируемой пользователем или сценарием.
Если навигация занимает слишком много времени (более четырех секунд в случае Chrome), то переход представления пропускается с помощью TimeoutError
DOMException
.
Демонстрация переходов между представлениями документов
Ознакомьтесь со следующей демонстрацией, в которой для создания демонстрации Stack Navigator используются переходы представлений. Здесь нет вызовов document.startViewTransition()
, переходы просмотра запускаются при переходе с одной страницы на другую.
Настройте переходы между представлениями документов
Чтобы настроить переходы между представлениями между документами, вы можете использовать некоторые функции веб-платформы.
Эти функции не являются частью самой спецификации View Transition API, но предназначены для использования вместе с ней.
События pageswap
и pagereveal
Чтобы вы могли настраивать переходы между представлениями документов, спецификация HTML включает два новых события, которые вы можете использовать: pageswap
и pagereveal
.
Эти два события запускаются для каждой навигации между документами с одним и тем же источником независимо от того, произойдет ли переход представления или нет. Если между двумя страницами должен произойти переход представления, вы можете получить доступ к объекту ViewTransition
, используя свойство viewTransition
для этих событий.
- Событие
pageswap
срабатывает перед отрисовкой последнего кадра страницы. Вы можете использовать это, чтобы внести некоторые изменения на исходящей странице в последнюю минуту, прямо перед тем, как будут сделаны старые снимки. - Событие
pagereveal
срабатывает на странице после ее инициализации или повторной активации, но до первой возможности отрисовки. С его помощью вы можете настроить новую страницу до того, как будут сделаны новые снимки.
Например, вы можете использовать эти события для быстрой установки или изменения некоторых значений view-transition-name
или передачи данных из одного документа в другой путем записи и чтения данных из sessionStorage
, чтобы настроить переход представления до его фактического запуска.
let lastClickX, lastClickY;
document.addEventListener('click', (event) => {
if (event.target.tagName.toLowerCase() === 'a') return;
lastClickX = event.clientX;
lastClickY = event.clientY;
});
// Write position to storage on old page
window.addEventListener('pageswap', (event) => {
if (event.viewTransition && lastClick) {
sessionStorage.setItem('lastClickX', lastClickX);
sessionStorage.setItem('lastClickY', lastClickY);
}
});
// Read position from storage on new page
window.addEventListener('pagereveal', (event) => {
if (event.viewTransition) {
lastClickX = sessionStorage.getItem('lastClickX');
lastClickY = sessionStorage.getItem('lastClickY');
}
});
Если хотите, вы можете пропустить переход в обоих событиях.
window.addEventListener("pagereveal", async (e) => {
if (e.viewTransition) {
if (goodReasonToSkipTheViewTransition()) {
e.viewTransition.skipTransition();
}
}
}
Объект ViewTransition
в pageswap
и pagereveal
— это два разных объекта. Они также по-разному обрабатывают различные обещания :
-
pageswap
: как только документ скрыт, старый объектViewTransition
пропускается. Когда это происходит,viewTransition.ready
отклоняет, аviewTransition.finished
разрешает. -
pagereveal
: ПромисupdateCallBack
на данный момент уже решен. Вы можете использовать промисыviewTransition.ready
иviewTransition.finished
.
Информация об активации навигации
В событиях pageswap
и pagereveal
вы также можете предпринимать действия на основе URL-адресов старых и новых страниц.
Например, в MPA Stack Navigator тип используемой анимации зависит от пути навигации:
- При переходе со страницы обзора на страницу сведений новый контент должен перемещаться справа налево.
- При переходе со страницы сведений на страницу обзора старый контент должен перемещаться слева направо.
Для этого вам нужна информация о навигации, которая, в случае с pageswap
, вот-вот произойдет или, в случае с pagereveal
, только что произошла.
Для этого браузеры теперь могут предоставлять объекты NavigationActivation
, которые содержат информацию о навигации одного и того же источника. Этот объект предоставляет используемый тип навигации, текущие и конечные записи истории пунктов назначения, которые можно найти в методе navigation.entries()
из API навигации .
На активированной странице вы можете получить доступ к этому объекту через navigation.activation
. В событии pageswap
вы можете получить к этому доступ через e.activation
.
Ознакомьтесь с этой демонстрацией профилей , которая использует информацию NavigationActivation
в событиях pageswap
и pagereveal
для установки значений view-transition-name
для элементов, которые должны участвовать в переходе представления.
Таким образом, вам не придется заранее украшать каждый элемент списка view-transition-name
. Вместо этого это происходит «точно в срок» с использованием JavaScript, только для тех элементов, которые в этом нуждаются.
Код выглядит следующим образом:
// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
if (e.viewTransition) {
const targetUrl = new URL(e.activation.entry.url);
// Navigating to a profile page
if (isProfilePage(targetUrl)) {
const profile = extractProfileNameFromUrl(targetUrl);
// Set view-transition-name values on the clicked row
document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';
// Remove view-transition-names after snapshots have been taken
// (this to deal with BFCache)
await e.viewTransition.finished;
document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
}
}
});
// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
if (e.viewTransition) {
const fromURL = new URL(navigation.activation.from.url);
const currentURL = new URL(navigation.activation.entry.url);
// Navigating from a profile page back to the homepage
if (isProfilePage(fromURL) && isHomePage(currentURL)) {
const profile = extractProfileNameFromUrl(currentURL);
// Set view-transition-name values on the elements in the list
document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';
// Remove names after snapshots have been taken
// so that we're ready for the next navigation
await e.viewTransition.ready;
document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
}
}
});
Код также очищает себя, удаляя значения view-transition-name
после запуска перехода представления. Таким образом, страница готова к последовательной навигации, а также может обрабатывать обход истории.
Чтобы помочь в этом, используйте эту служебную функцию, которая временно устанавливает view-transition-name
.
const setTemporaryViewTransitionNames = async (entries, vtPromise) => {
for (const [$el, name] of entries) {
$el.style.viewTransitionName = name;
}
await vtPromise;
for (const [$el, name] of entries) {
$el.style.viewTransitionName = '';
}
}
Предыдущий код теперь можно упростить следующим образом:
// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
if (e.viewTransition) {
const targetUrl = new URL(e.activation.entry.url);
// Navigating to a profile page
if (isProfilePage(targetUrl)) {
const profile = extractProfileNameFromUrl(targetUrl);
// Set view-transition-name values on the clicked row
// Clean up after the page got replaced
setTemporaryViewTransitionNames([
[document.querySelector(`#${profile} span`), 'name'],
[document.querySelector(`#${profile} img`), 'avatar'],
], e.viewTransition.finished);
}
}
});
// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
if (e.viewTransition) {
const fromURL = new URL(navigation.activation.from.url);
const currentURL = new URL(navigation.activation.entry.url);
// Navigating from a profile page back to the homepage
if (isProfilePage(fromURL) && isHomePage(currentURL)) {
const profile = extractProfileNameFromUrl(currentURL);
// Set view-transition-name values on the elements in the list
// Clean up after the snapshots have been taken
setTemporaryViewTransitionNames([
[document.querySelector(`#${profile} span`), 'name'],
[document.querySelector(`#${profile} img`), 'avatar'],
], e.viewTransition.ready);
}
}
});
Подождите, пока контент загрузится с блокировкой рендеринга
В некоторых случаях вам может потребоваться отложить первую визуализацию страницы до тех пор, пока определенный элемент не появится в новом DOM. Это позволяет избежать мигания и обеспечить стабильность состояния, в котором вы анимируете.
В <head>
определите один или несколько идентификаторов элементов, которые должны присутствовать перед первой отрисовкой страницы, используя следующий метатег.
<link rel="expect" blocking="render" href="#section1">
Этот метатег означает, что элемент должен присутствовать в DOM, а не загружать контент. Например, в случае с изображениями простого присутствия тега <img>
с указанным id
в дереве DOM достаточно, чтобы условие было оценено как истинное. Само изображение все еще может загружаться.
Прежде чем приступить к блокировке рендеринга, имейте в виду, что инкрементальный рендеринг является фундаментальным аспектом Интернета, поэтому будьте осторожны, выбирая блокировку рендеринга. Влияние блокировки рендеринга необходимо оценивать в каждом конкретном случае. По умолчанию избегайте использования blocking=render
если только вы не можете активно измерять и оценивать влияние, которое оно оказывает на ваших пользователей, путем измерения влияния на ваши основные веб-показатели .
Просмотр типов переходов в переходах между представлениями документов
Переходы между представлениями между документами также поддерживают типы перехода представлений для настройки анимации и выбора элементов, которые будут захватываться.
Например, при переходе на следующую или предыдущую страницу нумерации страниц вы можете использовать разные анимации в зависимости от того, переходите ли вы на более высокую или нижнюю страницу последовательности.
Чтобы установить эти типы заранее, добавьте типы в правило @view-transition
:
@view-transition {
navigation: auto;
types: slide, forwards;
}
Чтобы задать типы «на лету», используйте события pageswap
и pagereveal
для управления значением e.viewTransition.types
.
window.addEventListener("pagereveal", async (e) => {
if (e.viewTransition) {
const transitionType = determineTransitionType(navigation.activation.from, navigation.activation.entry);
e.viewTransition.types.add(transitionType);
}
});
Типы не переносятся автоматически из объекта ViewTransition
на старой странице в объект ViewTransition
на новой странице. Вам необходимо определить тип(ы), которые будут использоваться по крайней мере на новой странице, чтобы анимация работала должным образом.
Чтобы реагировать на эти типы, используйте селектор псевдокласса :active-view-transition-type()
так же, как и при переходах между представлениями одного и того же документа.
/* Determine what gets captured when the type is forwards or backwards */
html:active-view-transition-type(forwards, backwards) {
:root {
view-transition-name: none;
}
article {
view-transition-name: content;
}
.pagination {
view-transition-name: pagination;
}
}
/* Animation styles for forwards type only */
html:active-view-transition-type(forwards) {
&::view-transition-old(content) {
animation-name: slide-out-to-left;
}
&::view-transition-new(content) {
animation-name: slide-in-from-right;
}
}
/* Animation styles for backwards type only */
html:active-view-transition-type(backwards) {
&::view-transition-old(content) {
animation-name: slide-out-to-right;
}
&::view-transition-new(content) {
animation-name: slide-in-from-left;
}
}
/* Animation styles for reload type only */
html:active-view-transition-type(reload) {
&::view-transition-old(root) {
animation-name: fade-out, scale-down;
}
&::view-transition-new(root) {
animation-delay: 0.25s;
animation-name: fade-in, scale-up;
}
}
Поскольку типы применяются только к активному переходу вида, типы автоматически очищаются после завершения перехода вида. По этой причине типы хорошо работают с такими функциями, как BFCache .
Демо
В следующей демонстрации нумерации страниц содержимое страницы перемещается вперед или назад в зависимости от номера страницы, на которую вы переходите.
Используемый тип перехода определяется в событиях pagereveal
и pageswap
путем просмотра URL-адресов «в» и «от».
const determineTransitionType = (fromNavigationEntry, toNavigationEntry) => {
const currentURL = new URL(fromNavigationEntry.url);
const destinationURL = new URL(toNavigationEntry.url);
const currentPathname = currentURL.pathname;
const destinationPathname = destinationURL.pathname;
if (currentPathname === destinationPathname) {
return "reload";
} else {
const currentPageIndex = extractPageIndexFromPath(currentPathname);
const destinationPageIndex = extractPageIndexFromPath(destinationPathname);
if (currentPageIndex > destinationPageIndex) {
return 'backwards';
}
if (currentPageIndex < destinationPageIndex) {
return 'forwards';
}
return 'unknown';
}
};
Обратная связь
Отзывы разработчиков всегда ценны. Чтобы поделиться, отправьте сообщение о проблеме в рабочую группу CSS на GitHub с предложениями и вопросами. Префикс проблемы с помощью [css-view-transitions]
. Если вы столкнетесь с ошибкой, вместо этого сообщите об ошибке в Chromium .