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

Когда переход представления происходит между двумя разными документами, это называется переходом представления между документами . Обычно это происходит в многостраничных приложениях (MPA). Переходы между представлениями между документами поддерживаются в Chrome начиная с Chrome 126.

Поддержка браузера

  • Хром: 126.
  • Край: 126.
  • Firefox: не поддерживается.
  • Предварительная версия технологии Safari: поддерживается.

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

  1. Браузер делает снимки элементов, которые имеют уникальное view-transition-name как на старой, так и на новой странице.
  2. DOM обновляется, пока рендеринг подавляется.
  3. И, наконец, переходы поддерживаются CSS-анимацией.

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

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

  • Оба документа должны существовать в одном и том же источнике.
  • Обе страницы должны дать согласие, чтобы разрешить переход вида.

Оба эти условия объяснены далее в этом документе.


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

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

Происхождение страницы — это комбинация используемой схемы, имени хоста и порта, как подробно описано на web.dev .

Пример URL-адреса с выделенной схемой, именем хоста и портом. В совокупности они образуют начало.
Пример URL-адреса с выделенной схемой, именем хоста и портом. В совокупности они образуют начало.

Например, вы можете иметь переход между представлениями документов при переходе от 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() , переходы просмотра запускаются при переходе с одной страницы на другую.

Запись демо-версии Stack Navigator . Требуется Chrome 126+.

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

Чтобы настроить переходы между представлениями документов, вы можете использовать некоторые функции веб-платформы.

Эти функции не являются частью самой спецификации View Transition API, но предназначены для использования вместе с ней.

События pageswap и pagereveal

Поддержка браузера

  • Хром: 124.
  • Край: 124.
  • Firefox: не поддерживается.
  • Сафари: 18.2.

Источник

Чтобы вы могли настраивать переходы между представлениями документов, спецификация 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 .

Поддержка браузера

  • Хром: 123.
  • Край: 123.
  • Firefox: не поддерживается.
  • Сафари: не поддерживается.

Источник

В событиях 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, только для тех элементов, которые в этом нуждаются.

Запись демо-версии Profiles . Требуется Chrome 126+.

Код выглядит следующим образом:

// 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 s.

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);
    }
  }
});

Подождите, пока контент загрузится с блокировкой рендеринга

Поддержка браузера

  • Хром: 124.
  • Край: 124.
  • Firefox: не поддерживается.
  • Сафари: не поддерживается.

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

В <head> определите один или несколько идентификаторов элементов, которые должны присутствовать перед первой отрисовкой страницы, используя следующий метатег.

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

Этот метатег означает, что элемент должен присутствовать в DOM, а не загружать контент. Например, в случае с изображениями простого присутствия тега <img> с указанным id в дереве DOM достаточно, чтобы условие приняло значение true. Само изображение все еще может загружаться.

Прежде чем приступить к блокировке рендеринга, имейте в виду, что инкрементный рендеринг является фундаментальным аспектом Интернета, поэтому будьте осторожны, выбирая блокировку рендеринга. Влияние блокировки рендеринга необходимо оценивать в каждом конкретном случае. По умолчанию избегайте использования 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 .

Демо

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

Запись демо-версии пагинации (MPA) . Он использует разные переходы в зависимости от того, на какую страницу вы переходите.

Используемый тип перехода определяется в событиях 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 .