Transitions d'affichage entre les documents pour les applications multipages

Lorsqu'une transition de vue se produit entre deux documents différents, on parle de transition de vue entre documents. C'est généralement le cas dans les applications multipages. Les transitions de vue entre les documents sont compatibles avec Chrome à partir de la version 126.

Navigateurs pris en charge

  • Chrome: 126.
  • Edge: 126.
  • Firefox: non compatible.
  • Safari Technology Preview: compatible.

Les transitions de vue entre les documents reposent sur les mêmes composants et principes que les transitions de vue dans le même document, ce qui est très intentionnel:

  1. Le navigateur prend des instantanés des éléments qui ont un view-transition-name unique à la fois sur l'ancienne et la nouvelle page.
  2. Le DOM est mis à jour pendant que le rendu est supprimé.
  3. Enfin, les transitions sont basées sur des animations CSS.

Contrairement aux transitions de vue dans le même document, vous n'avez pas besoin d'appeler document.startViewTransition pour démarrer une transition de vue entre des documents. Au lieu de cela, le déclencheur d'une transition de vue entre documents est une navigation de même origine d'une page à une autre, une action généralement effectuée par l'utilisateur de votre site Web en cliquant sur un lien.

En d'autres termes, il n'existe aucune API à appeler pour lancer une transition de vue entre deux documents. Toutefois, deux conditions doivent être remplies:

  • Les deux documents doivent provenir de la même origine.
  • Les deux pages doivent activer cette fonctionnalité pour permettre la transition d'affichage.

Ces deux conditions sont expliquées plus loin dans ce document.


Les transitions de vue entre les documents sont limitées aux navigations de même origine.

Les transitions de vue entre les documents sont limitées aux navigations de même origine uniquement. Une navigation est considérée comme étant de même origine si l'origine des deux pages participantes est la même.

L'origine d'une page est une combinaison du schéma, du nom d'hôte et du port utilisés, comme indiqué sur web.dev.

Exemple d'URL avec le schéma, le nom d'hôte et le port mis en évidence. Ensemble, ils forment l'origine.
Exemple d'URL avec le schéma, le nom d'hôte et le port mis en évidence. Ensemble, ils constituent l'origine.

Par exemple, vous pouvez avoir une transition de vue entre les documents lorsque vous passez de developer.chrome.com à developer.chrome.com/blog, car ils ont la même origine. Vous ne pouvez pas effectuer cette transition lorsque vous passez de developer.chrome.com à www.chrome.com, car ces éléments sont multi-origines et sur le même site.


Les transitions d'affichage entre les documents sont activées par défaut

Pour que la transition entre deux documents soit possible, les deux pages participantes doivent activer cette fonctionnalité. Pour ce faire, utilisez la règle d'instruction @view-transition en CSS.

Dans la règle at-rule @view-transition, définissez le descripteur navigation sur auto pour activer les transitions de vue pour les navigations multi-documents de même origine.

@view-transition {
  navigation: auto;
}

En définissant le descripteur navigation sur auto, vous autorisez les transitions de vue pour les NavigationType suivants:

  • traverse
  • push ou replace, si l'activation n'a pas été lancée par l'utilisateur via les mécanismes d'UI du navigateur.

Les navigations exclues de auto incluent, par exemple, la navigation à l'aide de la barre d'adresse de l'URL ou le clic sur un favori, ainsi que toute forme de rechargement déclenchée par l'utilisateur ou par un script.

Si une navigation prend trop de temps (plus de quatre secondes dans le cas de Chrome), la transition de vue est ignorée avec un TimeoutError DOMException.

Démonstration des transitions entre les vues de documents

Regardez la démonstration suivante qui utilise des transitions de vue pour créer une démonstration du Stack Navigator. Il n'y a pas d'appels à document.startViewTransition() ici. Les transitions de vue sont déclenchées par la navigation d'une page à une autre.

Enregistrement de la démo de Stack Navigator. Nécessite Chrome 126 ou version ultérieure.

Personnaliser les transitions entre les vues de documents

Pour personnaliser les transitions de vue entre les documents, vous pouvez utiliser certaines fonctionnalités de la plate-forme Web.

Ces fonctionnalités ne font pas partie de la spécification de l'API View Transition, mais sont conçues pour être utilisées en conjonction avec elle.

Événements pageswap et pagereveal

Navigateurs pris en charge

  • Chrome: 124.
  • Edge: 124.
  • Firefox: non compatible.
  • Safari: 18.2

Source

Pour vous permettre de personnaliser les transitions de vue entre les documents, la spécification HTML inclut deux nouveaux événements que vous pouvez utiliser: pageswap et pagereveal.

Ces deux événements sont déclenchés pour chaque navigation interdocuments de même origine, que la transition de vue soit sur le point de se produire ou non. Si une transition de vue est sur le point de se produire entre les deux pages, vous pouvez accéder à l'objet ViewTransition à l'aide de la propriété viewTransition sur ces événements.

  • L'événement pageswap se déclenche avant l'affichage du dernier frame d'une page. Vous pouvez l'utiliser pour apporter des modifications de dernière minute à la page sortante, juste avant que les anciens instantanés ne soient créés.
  • L'événement pagereveal se déclenche sur une page après son initialisation ou sa réactivation, mais avant la première opportunité de rendu. Vous pouvez ainsi personnaliser la nouvelle page avant que les nouveaux instantanés ne soient créés.

Par exemple, vous pouvez utiliser ces événements pour définir ou modifier rapidement certaines valeurs view-transition-name, ou transmettre des données d'un document à un autre en écrivant et en lisant des données à partir de sessionStorage afin de personnaliser la transition de vue avant son exécution.

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

Si vous le souhaitez, vous pouvez ignorer la transition dans les deux événements.

window.addEventListener("pagereveal", async (e) => {
  if (e.viewTransition) {
    if (goodReasonToSkipTheViewTransition()) {
      e.viewTransition.skipTransition();
    }
  }
}

Les objets ViewTransition dans pageswap et pagereveal sont deux objets différents. Ils gèrent également les différentes promesses différemment:

  • pageswap: une fois le document masqué, l'ancien objet ViewTransition est ignoré. Dans ce cas, viewTransition.ready rejette la requête et viewTransition.finished la résout.
  • pagereveal: la promesse updateCallBack est déjà résolue à ce stade. Vous pouvez utiliser les promesses viewTransition.ready et viewTransition.finished.

Navigateurs pris en charge

  • Chrome: 123.
  • Edge: 123.
  • Firefox: non compatible.
  • Safari: non compatible.

Source

Dans les événements pageswap et pagereveal, vous pouvez également effectuer des actions en fonction des URL des anciennes et nouvelles pages.

Par exemple, dans le navigateur de pile MPA, le type d'animation à utiliser dépend du chemin de navigation:

  • Lorsque vous passez de la page d'aperçu à une page d'informations, le nouveau contenu doit glisser de droite à gauche.
  • Lorsque vous passez de la page d'informations à la page d'aperçu, l'ancien contenu doit glisser de gauche à droite.

Pour ce faire, vous avez besoin d'informations sur la navigation qui, dans le cas de pageswap, est sur le point de se produire ou, dans le cas de pagereveal, vient de se produire.

Pour ce faire, les navigateurs peuvent désormais exposer des objets NavigationActivation qui contiennent des informations sur la navigation au même origine. Cet objet expose le type de navigation utilisé, les entrées de l'historique de la destination actuelle et finale, comme indiqué dans navigation.entries() de l'API Navigation.

Sur une page activée, vous pouvez accéder à cet objet via navigation.activation. Dans l'événement pageswap, vous pouvez y accéder via e.activation.

Découvrez cette démonstration de Profiles qui utilise des informations NavigationActivation dans les événements pageswap et pagereveal pour définir les valeurs view-transition-name sur les éléments qui doivent participer à la transition de vue.

Vous n'avez donc pas besoin de décorer chaque élément de la liste avec un view-transition-name à l'avance. Au lieu de cela, cela se produit juste à temps à l'aide de JavaScript, uniquement sur les éléments qui en ont besoin.

Enregistrement de la démonstration des profils. Nécessite Chrome 126 ou version ultérieure.

Le code est le suivant :

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

Le code effectue également un nettoyage en supprimant les valeurs view-transition-name après l'exécution de la transition de vue. La page est ainsi prête pour les navigations successives et peut également gérer la traversée de l'historique.

Pour vous aider, utilisez cette fonction d'utilitaire qui définit temporairement des 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 = '';
  }
}

Le code précédent peut maintenant être simplifié comme suit:

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

Attendre le chargement du contenu avec le blocage du rendu

Navigateurs pris en charge

  • Chrome: 124.
  • Edge: 124.
  • Firefox: non compatible.
  • Safari: non compatible.

Dans certains cas, vous pouvez retarder le premier rendu d'une page jusqu'à ce qu'un élément spécifique soit présent dans le nouveau DOM. Cela évite les clignotements et garantit que l'état vers lequel vous animez est stable.

Dans <head>, définissez un ou plusieurs ID d'élément qui doivent être présents avant le premier rendu de la page à l'aide de la balise Meta suivante.

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

Cette balise Meta signifie que l'élément doit être présent dans le DOM, et non que le contenu doit être chargé. Par exemple, avec les images, la simple présence de la balise <img> avec le id spécifié dans l'arborescence DOM suffit pour que la condition soit évaluée à "true". L'image elle-même est peut-être encore en cours de chargement.

Avant de bloquer complètement le rendu, sachez que le rendu incrémentiel est un aspect fondamental du Web. Par conséquent, soyez prudent lorsque vous choisissez de bloquer le rendu. L'impact du blocage du rendu doit être évalué au cas par cas. Par défaut, évitez d'utiliser blocking=render, sauf si vous pouvez mesurer et évaluer activement l'impact qu'il a sur vos utilisateurs, en mesurant l'impact sur vos Core Web Vitals.


Afficher les types de transition dans les transitions de vue entre documents

Les transitions de vue entre les documents sont également compatibles avec les types de transitions de vue pour personnaliser les animations et les éléments capturés.

Par exemple, lorsque vous passez à la page suivante ou à la page précédente d'une pagination, vous pouvez utiliser des animations différentes selon que vous passez à une page plus élevée ou plus basse de la séquence.

Pour définir ces types à l'avance, ajoutez-les dans la règle at-@view-transition:

@view-transition {
  navigation: auto;
  types: slide, forwards;
}

Pour définir les types instantanément, utilisez les événements pageswap et pagereveal pour manipuler la valeur de 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);
  }
});

Les types ne sont pas automatiquement transférés de l'objet ViewTransition de l'ancienne page à l'objet ViewTransition de la nouvelle page. Vous devez déterminer le ou les types à utiliser sur au moins la nouvelle page pour que les animations s'exécutent comme prévu.

Pour répondre à ces types, utilisez le sélecteur de pseudo-classe :active-view-transition-type() de la même manière que pour les transitions de vue dans le même document.

/* 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;
  }
}

Étant donné que les types ne s'appliquent qu'à une transition de vue active, ils sont automatiquement nettoyés à la fin d'une transition de vue. C'est pourquoi les types fonctionnent bien avec des fonctionnalités telles que BFCache.

Démo

Dans la démonstration de pagination suivante, le contenu de la page glisse vers l'avant ou vers l'arrière en fonction du numéro de page vers lequel vous naviguez.

Enregistrement de la démonstration de la pagination (MPA). Il utilise différentes transitions selon la page à laquelle vous accédez.

Le type de transition à utiliser est déterminé dans les événements pagereveal et pageswap en examinant les URL de destination et de départ.

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

Commentaires

Les commentaires des développeurs sont toujours les bienvenus. Pour partager vos suggestions et poser vos questions, signalez un problème au groupe de travail CSS sur GitHub. Ajoutez le préfixe [css-view-transitions] à votre problème. Si vous rencontrez un bug, signalez-le dans Chromium.