Transitions d'affichage entre les documents pour les applications multipages

Lorsqu'une transition d'affichage 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 d'affichage entre documents sont compatibles avec Chrome 126.

Navigateurs pris en charge

  • Chrome: 126 <ph type="x-smartling-placeholder">
  • Edge: 126 <ph type="x-smartling-placeholder">
  • Firefox: non compatible. <ph type="x-smartling-placeholder">
  • Safari: non compatible. <ph type="x-smartling-placeholder">

Les transitions d'affichage entre documents reposent sur les mêmes éléments de base et les mêmes principes que les transitions d'affichage sur un même document, ce qui est très intentionnel:

  1. Le navigateur prend des instantanés des éléments ayant un view-transition-name unique sur l'ancienne et la nouvelle page.
  2. Le DOM est mis à jour pendant la suppression du rendu.
  3. Enfin, les transitions sont optimisées par des animations CSS.

La différence avec les transitions d'affichage sur un même document est qu'avec les transitions d'affichage entre documents, vous n'avez pas besoin d'appeler document.startViewTransition pour démarrer une transition de vue. Le déclencheur d'une transition entre documents est une navigation d'origine identique d'une page à une autre, une action généralement effectuée par l'utilisateur de votre site Web qui clique sur un lien.

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

  • Les deux documents doivent se trouver sur la même origine.
  • Pour permettre la transition d'affichage, vous devez activer la fonctionnalité sur les deux pages.

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


Les transitions d'affichage entre documents sont limitées aux navigations d'origine identique

Les transitions d'affichage entre documents sont limitées aux navigations d'origine identique uniquement. Une navigation est considérée comme ayant la même origine si l'origine des deux pages participantes est identique.

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

<ph type="x-smartling-placeholder">
</ph> Exemple d&#39;URL avec le schéma, le nom d&#39;hôte et le port mis en évidence Ensemble, ils forment l&#39;origine.
Exemple d'URL avec le schéma, le nom d'hôte et le port mis en évidence Ensemble, ils forment l'origine.

Par exemple, vous pouvez effectuer une transition de vue entre documents lorsque vous passez de developer.chrome.com à developer.chrome.com/blog, car il s'agit de la même origine. Vous ne pouvez pas effectuer cette transition lorsque vous naviguez de developer.chrome.com vers www.chrome.com, car il s'agit d'éléments multi-origines et sur un même site.


Les transitions d'affichage entre documents doivent être activées.

Pour effectuer une transition entre deux documents, les deux pages participantes doivent l'autoriser. Pour ce faire, utilisez l'attribut "at-rule" de @view-transition dans le CSS.

Dans la règle @view-transition, définissez le descripteur navigation sur auto pour activer les transitions de vue pour les navigations d'origine commune entre documents.

@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é initiée par l'utilisateur via les mécanismes de l'interface utilisateur du navigateur.

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

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

Démonstration des transitions entre les vues entre documents

La démonstration suivante utilise des transitions de vue pour créer une démonstration Stack Navigator. Il n'y a pas d'appels à document.startViewTransition() ici. Les transitions de vue sont déclenchées lorsque vous passez d'une page à une autre.

<ph type="x-smartling-placeholder">
</ph>
Enregistrement de la démonstration de Stack Navigator Nécessite Chrome 126 ou version ultérieure.

Personnaliser les transitions entre les documents

Pour personnaliser les transitions 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 proprement dite, mais sont conçues pour être utilisées conjointement avec elle.

Événements pageswap et pagereveal

Navigateurs pris en charge

  • Chrome: 124 <ph type="x-smartling-placeholder">
  • Edge: 124 <ph type="x-smartling-placeholder">
  • Firefox: non compatible. <ph type="x-smartling-placeholder">
  • Safari: non compatible. <ph type="x-smartling-placeholder">

Source

Pour vous permettre de personnaliser les transitions 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 multi-document d'origine identique, qu'une 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 effectuer des modifications de dernière minute sur la page sortante, juste avant la prise des anciens instantané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 la prise des nouveaux instantanés.

Par exemple, vous pouvez utiliser ces événements pour définir ou modifier rapidement certaines valeurs view-transition-name, ou pour 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 d'affichage avant son exécution effective.

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 choisir d'ignorer la transition pour les deux événements.

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

L'objet ViewTransition dans pageswap et pagereveal sont deux objets différents. De plus, ils gèrent 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 demande et viewTransition.finished est résolue.
  • 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 <ph type="x-smartling-placeholder">
  • Edge: 123 <ph type="x-smartling-placeholder">
  • Firefox: non compatible. <ph type="x-smartling-placeholder">
  • Safari: non compatible. <ph type="x-smartling-placeholder">

Source

Vous pouvez également effectuer des actions en fonction des URL des anciennes et des nouvelles pages dans les événements pageswap et pagereveal.

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

  • Lorsque vous passez de la page "Vue d'ensemble" à la page d'informations, le nouveau contenu doit glisser de droite à gauche.
  • Lorsque vous passez de la page d'informations à la page "Vue d'ensemble", l'ancien contenu doit disparaître 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, qui vient de se produire.

Pour cela, les navigateurs peuvent désormais exposer des objets NavigationActivation qui contiennent des informations sur la navigation d'origine identique. Cet objet expose le type de navigation utilisé, ainsi que les entrées actuelles et finales de l'historique de destination, telles qu'elles apparaissent dans navigation.entries() à partir 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.

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

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

<ph type="x-smartling-placeholder">
</ph>
Enregistrement de la démo sur les 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 après lui-même en supprimant les valeurs view-transition-name après l'exécution de la transition de vue. Ainsi, la page est prête pour les navigations successives et peut également gérer le balayage de l'historique.

Pour faciliter cette opération, utilisez cette fonction utilitaire qui définit temporairement les 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 en bloquant l'affichage

Navigateurs pris en charge

  • Chrome: 124 <ph type="x-smartling-placeholder">
  • Edge: 124 <ph type="x-smartling-placeholder">
  • Firefox: non compatible. <ph type="x-smartling-placeholder">
  • Safari: non compatible. <ph type="x-smartling-placeholder">

Dans certains cas, il se peut que vous souhaitiez suspendre le premier affichage d'une page jusqu'à ce qu'un certain élément soit présent dans le nouveau DOM. Cela évite de clignoter et garantit que l'état vers lequel vous effectuez l'animation est stable.

Dans <head>, définissez un ou plusieurs ID d'éléments qui doivent être présents avant que la page ne soit affichée pour le premier, à 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, pour les images, la simple présence de la balise <img> avec le id spécifié dans l'arborescence DOM suffit pour que la condition renvoie la valeur "true". Il est possible que l'image elle-même soit toujours en cours de chargement.

Avant de vous lancer dans le blocage de l'affichage, sachez que l'affichage incrémentiel est un aspect fondamental du Web. Vous devez donc être prudent lorsque vous choisissez de bloquer l'affichage. L'impact du blocage de l'affichage doit être évalué au cas par cas. Par défaut, évitez d'utiliser blocking=render, sauf si vous pouvez mesurer et évaluer activement son impact 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 d'affichage entre documents sont également compatibles avec les types de transition d'affichage permettant de personnaliser les animations et les éléments capturés.

Par exemple, lorsque vous passez à la page suivante ou précédente dans une pagination, vous pouvez utiliser différentes animations selon que vous accédez à une page supérieure ou à une page inférieure 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 à la volée, 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 vers l'objet ViewTransition de la nouvelle. Vous devez déterminer le ou les types à utiliser au moins sur la nouvelle page afin 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 du 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 Active View, ils sont automatiquement nettoyés à la fin de la transition. Pour cette raison, les types fonctionnent bien avec des fonctionnalités telles que BFCache.

Démo

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

<ph type="x-smartling-placeholder">
</ph>
Enregistrement de la démonstration de Pagination (MPA). Elle utilise différentes transitions selon la page que vous consultez.

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

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 commentaires, signalez un problème au groupe de travail CSS sur GitHub en envoyant vos suggestions et vos questions. Ajoutez un préfixe [css-view-transitions] à votre problème. Si vous rencontrez un bug, signalez-le plutôt.