Le problème
Les transitions de vue entre documents animent les éléments entre deux pages lors d'une navigation de même origine. Le navigateur capture un instantané de l'ancienne page, accède à la nouvelle page, puis l'anime à partir de l'instantané. Si la nouvelle page n'a pas fini de charger les ressources critiques (feuilles de style, scripts de mise en page ou éléments DOM clés), la transition s'anime vers un état incomplet ou sans style. Cela entraîne des problèmes visuels, tels que des éléments qui se déplacent vers des positions incorrectes, un réajustement du contenu au milieu de l'animation ou des polices de remplacement qui clignotent pour devenir des polices Web une fois la transition terminée.
La solution
Utilisez blocking="render" sur les éléments <link> et <script> critiques dans l'élément <head> de la nouvelle page, et utilisez <link rel="expect"> pour bloquer l'affichage jusqu'à ce que des éléments DOM spécifiques aient été analysés. Cela garantit que le navigateur ne commence pas l'animation de transition de vue tant que l'état visuel de la nouvelle page n'est pas stable. Le navigateur continue d'analyser le code HTML en arrière-plan. Seul l'affichage est différé.
Stratégie d'implémentation
- OBLIGATOIRE : Activez les transitions de vue entre documents avec la règle CSS
@view-transitionsur les deux pages. - OBLIGATOIRE : Assurez-vous que les feuilles de style critiques se trouvent dans
<head>. Les feuilles de style de l'élément<head>bloquent l'affichage par défaut. Les feuilles de style injectées de manière dynamique nécessitent unblocking="render"explicite. - À FAIRE : Utilisez
blocking="render"sur les éléments<script>qui doivent s'exécuter avant l'animation de la transition (par exemple, les scripts qui appliquent un thème ou affectent la mise en page). - À FAIRE : Utilisez
<link rel="expect" href="#element-id" blocking="render">pour bloquer l'affichage jusqu'à ce que le contenu au-dessus de la ligne de flottaison ait été analysé. Cela s'applique à tous les types de transitions : fondus enchaînés pleine page (pour éviter d'animer une page vide), animations de morphing (pour s'assurer que les éléments nommés existent dans le DOM) et mises en page dépendantes des scripts (pour s'assurer que le contenu stylisé est analysé). - NE BLOQUEZ PAS l'affichage du contenu non critique. Ne bloquez que les ressources et les éléments qui affectent la fenêtre d'affichage initiale. Le blocage d'un trop grand nombre de contenus retarde la transition et dégrade les performances perçues.
Guide d'implémentation
Étape 1 : Activez les transitions de vue entre documents
OBLIGATOIRE : Les pages source et de destination doivent inclure la règle @ @view-transition. Sans cela, aucune transition entre documents ne se produit.
/*
MANDATORY: Include this rule in every page that participates
in cross-document view transitions.
`navigation: auto` enables transitions for standard navigations
(link clicks, form submissions, back/forward).
*/
@view-transition {
navigation: auto;
}
/* MANDATORY Copy-Paste Safety: Disable cross-document view transitions for users requesting reduced motion */
@media (prefers-reduced-motion: reduce) {
@view-transition {
navigation: none;
}
}
Étape 2 : Bloquer l'affichage jusqu'à l'exécution des scripts critiques
Si un script non bloquant dans <head> doit s'exécuter avant l'animation de la transition (par exemple, pour appliquer une classe de thème ou affecter la mise en page), marquez-le avec blocking="render". Sans cela, les scripts async, defer ou type="module" peuvent s'exécuter après le début de la transition.
<head>
<!--
DO: Mark layout-critical scripts with blocking="render".
-->
<script type=module blocking="render">
// Example: apply a stored theme before the page renders,
// so the transition snapshot reflects the correct theme.
document.documentElement.dataset.theme =
localStorage.getItem('theme') || 'light';
</script>
</head>
Étape 3 : Bloquer l'affichage jusqu'à ce que les éléments DOM clés soient analysés
Les feuilles de style et les scripts blocking="render" dans l'élément <head> garantissent uniquement que l'élément <head> a été entièrement traité. Ils n'attendent pas l'analyse du contenu <body>. Sans blocage supplémentaire, le navigateur peut prendre l'instantané de la nouvelle page avant que les éléments au-dessus de la ligne de flottaison n'existent dans le DOM, ce qui entraîne une transition qui s'anime vers une page vide ou partiellement affichée.
<link rel="expect"> résout ce problème en bloquant l'affichage jusqu'à ce qu'un élément spécifique (identifié par son id) ait été analysé. La valeur href doit être un identifiant de fragment (par exemple, #hero) correspondant à l'attribut id de l'élément cible. Une fois la balise de fermeture de cet élément analysée, le bloc de rendu est libéré. Notez que <link rel="expect"> garantit uniquement que l'élément DOM cible existe dans le document. Il n'attend pas que les sous-ressources externes (comme les pixels <img>) soient téléchargées ou décodées.
À FAIRE : Utilisez <link rel="expect"> dans tous les scénarios suivants :
Cas d'utilisation 1 : fondu enchaîné pleine page
Même si aucun élément individuel n'a de view-transition-name, la transition root par défaut effectue un fondu enchaîné sur l'ensemble de la page. Si l'instantané de la nouvelle page est pris avant l'analyse du contenu au-dessus de la ligne de flottaison, le fondu enchaîné passe de l'ancienne page à une page vide ou incomplète. Bloquez l'affichage sur un élément qui marque la fin du contenu visible au-dessus de la ligne de flottaison.
<head>
<link rel="stylesheet" href="/css/styles.css">
<!--
DO: Block rendering until the main content area is parsed,
even for a simple cross-fade. Without this, the browser may
snapshot the page before visible content exists in the DOM,
causing the cross-fade to reveal a blank or partial page.
-->
<link rel="expect" href="#main-content" blocking="render">
</head>
<body>
<header>...</header>
<main id="main-content">
<h1>Page Title</h1>
<p>Above-the-fold content the user should see immediately.</p>
</main>
<!-- Content below the fold does NOT need to be blocked on -->
<section>...</section>
</body>
Cas d'utilisation 2 : Animations de morphing entre des éléments spécifiques
Lorsque des éléments des deux pages partagent un view-transition-name, le navigateur les transforme de manière fluide tout au long de la navigation. Si l'élément cible n'a pas été analysé au début de la transition, le navigateur ne peut pas le trouver. Le morphing se dégrade en animations de sortie et d'entrée distinctes. Bloquez l'affichage jusqu'à ce que l'élément avec l'attribut view-transition-name ait été analysé.
Étant donné que <link rel="expect"> ne suspend l'affichage que jusqu'à ce que l'élément DOM cible soit analysé et n'attend pas que les données d'image soient téléchargées ou décodées, un <img> non mis en cache peut être capturé sous la forme d'un rectangle vide dans l'instantané de la nouvelle page. Pour éviter les instantanés vides ou les décalages de mise en page lors des transformations d'images, préchargez ou prédécodez les images de transition critiques (par exemple, avec <link rel="preload" as="image"> ou en prérendant la page de destination) et fournissez des solutions de remplacement explicites pour le dimensionnement (attributs width et height ou CSS aspect-ratio, ainsi qu'une couleur d'arrière-plan d'espace réservé).
<head>
<link rel="stylesheet" href="/css/styles.css">
<!--
DO: Preload critical transition images so their pixels are
available to decode before the new-page snapshot is captured.
<link rel="expect"> only waits for the DOM element to be parsed,
NOT for image pixels to download or decode.
-->
<link rel="preload" as="image" href="/img/product.webp" fetchpriority="high">
<!--
DO: Block rendering until the element participating in the
morph animation has been parsed. Without this, the browser
may start the transition before #hero exists, causing the
morph to degrade to a fade-out/fade-in.
-->
<link rel="expect" href="#hero" blocking="render">
<!--
When multiple blocking="render" resources are present,
rendering is blocked until ALL of them are satisfied.
Here, the browser waits for both the script to execute
AND the #hero element to be parsed — whichever comes last.
-->
<script async blocking="render" src="/js/transition-setup.js"></script>
</head>
<body>
<header>...</header>
<section id="hero">
<h1 style="view-transition-name: page-title">Product Name</h1>
<!--
DO: Provide explicit dimensions (width/height or aspect-ratio)
as a sizing fallback so the snapshot captures the correct layout
box even if image decoding is still in progress.
-->
<img
style="view-transition-name: hero-image"
src="/img/product.webp"
width="800"
height="600"
alt="Product"
>
</section>
</body>
Étape 4 : Utilisez des requêtes média pour le blocage du rendu responsif
Différentes tailles de fenêtre d'affichage peuvent afficher différentes quantités de contenu au-dessus de la ligne de flottaison. Utilisez l'attribut media sur <link rel="expect"> pour bloquer l'affichage uniquement pour le contenu visible à une largeur de fenêtre d'affichage donnée.
<head>
<!--
DO: Use media queries to conditionally block rendering.
On wide screens, both the hero and the sidebar are visible,
so block until both are parsed. On narrow screens, only the
hero is visible initially.
-->
<link
rel="expect"
href="#hero"
blocking="render"
media="screen and (width <= 768px)"
>
<link
rel="expect"
href="#sidebar"
blocking="render"
media="screen and (width > 768px)"
>
</head>
Étape 5 : Utilisez pagereveal pour les transitions dépendantes du contexte (facultatif)
L'événement pagereveal n'est pas obligatoire pour la stratégie principale de blocage de l'affichage. Il n'est nécessaire que lorsque les valeurs view-transition-name doivent être attribuées de manière dynamique en fonction de l'endroit d'où l'utilisateur a navigué (par exemple, pour transformer un élément de liste spécifique en titre de page d'informations).
Si les valeurs view-transition-name sont attribuées de manière statique en CSS ou si vous n'utilisez que le fondu enchaîné pleine page par défaut, ignorez complètement cette étape.
<head>
<!--
MANDATORY: The pagereveal listener must be registered before
the page renders. Inline small setup scripts with blocking="render"
to avoid an extra network round-trip on cold loads, or use an
external async script with blocking="render" so the listener is
registered early without blocking HTML parsing.
If the listener is registered too late (e.g., in a deferred
script), the event may have already fired.
-->
<script async blocking="render" src="/js/transition-setup.js"></script>
</head>
// transition-setup.js
window.addEventListener('pagereveal', async (event) => {
if (!event.viewTransition) return;
const from = navigation.activation?.from;
if (!from) return;
const fromUrl = new URL(from.url);
// DO: Assign view-transition-name based on navigation context.
// This enables a morph animation from the product card on the
// list page to the heading on the detail page.
if (fromUrl.pathname === '/products/') {
const heading = document.querySelector('main h1');
if (!heading) return;
heading.style.viewTransitionName = 'product-title';
try {
await event.viewTransition.ready;
} catch (error) {
// Skipped view transitions reject `ready` with InvalidStateError.
if (error.name !== 'InvalidStateError') {
throw error;
}
} finally {
// MANDATORY: Remove the temporary name after the transition
// finishes. Stale names interfere with subsequent navigations
// and prevent the page from entering the bfcache.
await event.viewTransition.finished;
heading.style.viewTransitionName = '';
}
}
});
Bonnes pratiques
- À FAIRE : attribuez
view-transition-namevia CSS dans la mesure du possible. Réservez l'attribution JavaScript (viapagereveal) aux cas où le nom dépend du contexte de navigation. - À FAIRE : Veillez à ce que les scripts bloquant l'affichage soient petits et rapides. Le navigateur dispose d'un délai avant expiration intégré (environ quatre secondes), après lequel la transition est entièrement ignorée (rejet de
viewTransition.readyavec unInvalidStateErrorsi une transition a été lancée). - N'UTILISEZ PAS
<link rel="expect">pour bloquer des éléments situés en bas de la page qui ne sont pas visibles dans la fenêtre d'affichage initiale. Cela retarde la transition sans avantage visuel. - NE MARQUEZ PAS les chargeurs d'analyse, de réseau publicitaire, de gestionnaire de balises ou d'autres chargeurs tiers avec
blocking="render", même s'ils injectent du contenu dans la fenêtre d'affichage initiale. Leur latence réseau est prise en compte dans le délai avant expiration de la transition, et leur sortie ne fait pas partie de l'état stable de la page. - NE PAS attribuer le même
view-transition-nameà plusieurs éléments sur la même page. Les noms en double entraînent l'annulation de l'ensemble de la transition. - Impact de la technologie d'assistance sur le timing : l'utilisation de
blocking="render"retarde les mises à jour visuelles et l'affichage initial. Bien que cela évite les problèmes visuels pour les utilisateurs voyants, cela peut entraîner une latence de traitement ou une initialisation différée pour les lecteurs d'écran et d'autres technologies d'assistance qui dépendent des arbres d'accessibilité affichés. Évaluez les avantages de la continuité visuelle par rapport à la latence de lecture initiale pour les utilisateurs non visuels, et assurez-vous que les scripts bloquant l'affichage sont minimes et extrêmement optimisés.
Stratégies de remplacement
Les transitions de vue entre documents sont un excellent candidat pour l'amélioration progressive. Dans les navigateurs qui ne les acceptent pas, la règle @view-transition est ignorée et les navigations standard de même origine se produisent exactement comme elles le feraient sans cette fonctionnalité. Les navigateurs compatibles bénéficient de transitions fluides, tandis que les autres bénéficient d'une navigation standard. La compatibilité limitée des navigateurs n'est pas une raison pour ne pas l'adopter.
Tous les navigateurs compatibles avec les transitions de vue entre documents sont également compatibles avec blocking="render" et <link rel="expect">. Aucune solution de remplacement distincte n'est donc nécessaire pour les fonctionnalités de blocage de l'affichage décrites dans ce guide.
Autres points à prendre en compte
- Impact sur les performances : chaque ressource bloquant l'affichage retarde le début de l'animation de transition de vue. Réduisez le nombre de scripts bloquant l'affichage et n'utilisez
<link rel="expect">que pour les éléments situés au-dessus de la ligne de flottaison. Préchargez les pages de destination à l'aide de l'API Speculation Rules pour éliminer complètement les délais de chargement. - Comportement du délai avant expiration : si le temps de blocage du rendu combiné dépasse environ quatre secondes, le navigateur ignore la transition (et toutes les promesses
viewTransition.readyactives sont rejetées avec unInvalidStateError). Assurez-vous que les ressources critiques se chargent bien dans ce délai. - Compatibilité avec le bfcache : les attributions temporaires de
view-transition-namequi ne sont pas nettoyées après la transition peuvent empêcher la page d'entrer dans le bfcache. Supprimez toujours les noms attribués de manière dynamique dans le rappelfinished.