Cuando se produce una transición de vistas entre dos documentos diferentes, se denomina transición de vista en varios documentos. Por lo general, este es el caso de las aplicaciones de varias páginas (MPA). Las transiciones de vista entre documentos son compatibles con Chrome a partir de la versión 126.
Navegadores compatibles
Las transiciones de vista entre documentos se basan en los mismos elementos y principios que las transiciones de vista del mismo documento, lo que es muy intencional:
- El navegador toma instantáneas de los elementos que tienen un
view-transition-name
único en la página anterior y en la nueva. - El DOM se actualiza mientras se suprime la renderización.
- Por último, las transiciones se basan en animaciones CSS.
La diferencia con las transiciones de vista del mismo documento es que, con las transiciones de vista entre documentos, no es necesario llamar a document.startViewTransition
para iniciar una transición de vista. En cambio, el activador de una transición de vista de varios documentos es una navegación del mismo origen de una página a otra, una acción que suele realizar el usuario de tu sitio web cuando hace clic en un vínculo.
En otras palabras, no hay una API a la cual llamar para iniciar una transición de vista entre dos documentos. Sin embargo, se deben cumplir dos condiciones:
- Ambos documentos deben existir en el mismo origen.
- Ambas páginas deben habilitarse para permitir la transición de vistas.
Ambas condiciones se explican más adelante en este documento.
Las transiciones de vista entre documentos se limitan a las navegaciones del mismo origen.
Las transiciones de vista entre documentos se limitan solo a las navegaciones del mismo origen. Una navegación se considera del mismo origen si el origen de ambas páginas participantes es el mismo.
El origen de una página es una combinación del esquema, el nombre de host y el puerto que se usan, como se detalló en web.dev.
Por ejemplo, puedes tener una transición de vista entre documentos cuando navegas de developer.chrome.com
a developer.chrome.com/blog
, ya que esas son del mismo origen.
No es posible realizar esa transición cuando navegas de developer.chrome.com
a www.chrome.com
, ya que son de origen cruzado y del mismo sitio.
Las transiciones de vista entre documentos son opcionales
Para tener una transición de vista entre documentos entre dos documentos, ambas páginas participantes deben habilitar esta opción. Esto se hace con la regla de anidación @view-transition
en CSS.
En la regla at @view-transition
, establece el descriptor navigation
en auto
para habilitar las transiciones de vistas en las navegaciones del mismo origen entre documentos.
@view-transition {
navigation: auto;
}
Si configuras el descriptor navigation
como auto
, aceptas que se produzcan transiciones de vistas en los siguientes objetos NavigationType:
traverse
push
oreplace
, si el usuario no inició la activación a través de los mecanismos de la IU del navegador
Las navegaciones excluidas de auto
son, por ejemplo, navegar con la barra de direcciones de URL o hacer clic en un favorito, así como cualquier forma de recarga iniciada por el usuario o la secuencia de comandos.
Si una navegación tarda demasiado (más de cuatro segundos en el caso de Chrome), se omite la transición de vista con un TimeoutError
DOMException
.
Demostración de transiciones de vista entre documentos
Consulta la siguiente demostración que usa transiciones de vista para crear una demostración de Stack Navigator. No hay llamadas a document.startViewTransition()
aquí, las transiciones de vista se activan cuando se navega de una página a otra.
Cómo personalizar las transiciones de vista entre documentos
Para personalizar las transiciones de vista entre documentos, puedes usar algunas funciones de la plataforma web.
- Los eventos
pageswap
ypagereveal
- Información sobre la activación de Navigation
- Bloqueo de renderización
Estas funciones no forman parte de la especificación de la API de View Transition en sí, pero están diseñadas para usarse en conjunto.
Los eventos pageswap
y pagereveal
Para que puedas personalizar las transiciones de vistas entre documentos, la especificación HTML incluye dos eventos nuevos que puedes usar: pageswap
y pagereveal
.
Estos dos eventos se activan para cada navegación entre documentos del mismo origen, independientemente de si está a punto de ocurrir una transición de vista o no. Si está a punto de producirse una transición de vista entre las dos páginas, puedes acceder al objeto ViewTransition
con la propiedad viewTransition
en estos eventos.
- El evento
pageswap
se activa antes de que se renderice el último marco de una página. Puedes usar esta opción para realizar algunos cambios de último minuto en la página saliente, justo antes de que se tomen las instantáneas anteriores. - El evento
pagereveal
se activa en una página después de que se inicializa o reactiva, pero antes de la primera oportunidad de renderización. Con ella, puedes personalizar la página nueva antes de que se tomen las instantáneas nuevas.
Por ejemplo, puedes usar estos eventos para establecer o cambiar rápidamente algunos valores de view-transition-name
o pasar datos de un documento a otro escribiendo y leyendo datos de sessionStorage
para personalizar la transición de vista antes de que se ejecute.
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 lo deseas, puedes omitir la transición en ambos eventos.
window.addEventListener("pagereveal", async (e) => {
if (e.viewTransition) {
if (goodReasonToSkipTheViewTransition()) {
e.viewTransition.skipTransition();
}
}
}
El objeto ViewTransition
en pageswap
y pagereveal
son dos objetos diferentes. También manejan las diferentes promesas de manera diferente:
pageswap
: Una vez que se oculta el documento, se omite el objetoViewTransition
anterior. Cuando eso sucede, se rechazaviewTransition.ready
y se resuelveviewTransition.finished
.pagereveal
: La promesaupdateCallBack
ya se resolvió en este punto. Puedes usar las promesasviewTransition.ready
yviewTransition.finished
.
Información de activación de Navigation
En los eventos pageswap
y pagereveal
, también puedes realizar acciones en función de las URLs de las páginas antiguas y nuevas.
Por ejemplo, en MPA Stack Navigator, el tipo de animación que se debe usar depende de la ruta de navegación:
- Cuando navegues desde la página de resumen hasta la página de detalles, el contenido nuevo debe deslizarse de derecha a izquierda.
- Cuando navegues de la página de detalles a la página de resumen, el contenido anterior debe deslizarse de izquierda a derecha.
Para ello, necesitas información sobre la navegación que, en el caso de pageswap
, está a punto de ocurrir o, en el caso de pagereveal
, acaba de ocurrir.
Para ello, los navegadores ahora pueden exponer objetos NavigationActivation
que contienen información sobre la navegación de origen. Este objeto expone el tipo de navegación utilizado, el actual y las entradas del historial de destino final, como se encuentra en navigation.entries()
de la API de Navigation.
En una página activada, puedes acceder a este objeto a través de navigation.activation
. En el evento pageswap
, puedes acceder a esto a través de e.activation
.
Consulta esta demostración de Profiles que usa información de NavigationActivation
en los eventos pageswap
y pagereveal
para establecer los valores de view-transition-name
en los elementos que deben participar en la transición de vista.
De esa manera, no tienes que decorar todos y cada uno de los elementos de la lista con un view-transition-name
por adelantado. En cambio, esto sucede justo a tiempo con JavaScript, solo en los elementos que lo necesitan.
El código es el siguiente:
// 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';
}
}
});
El código también limpia después de sí mismo, ya que quita los valores view-transition-name
después de que se ejecutó la transición de vista. De esta manera, la página está lista para navegaciones sucesivas y también puede controlar el recorrido del historial.
Para ayudar con esto, usa esta función de utilidad que establece view-transition-name
de forma temporal.
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 = '';
}
}
El código anterior ahora se puede simplificar de la siguiente manera:
// 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);
}
}
});
Espera a que se cargue el contenido con el bloqueo de renderización
Navegadores compatibles
En algunos casos, es posible que desees retrasar la primera renderización de una página hasta que un elemento determinado esté presente en el nuevo DOM. Esto evita que se produzcan parpadeos y garantiza que el estado al que animas sea estable.
En <head>
, usa la siguiente metaetiqueta para definir uno o más IDs de elementos que deben estar presentes antes de que la página obtenga su primera renderización.
<link rel="expect" blocking="render" href="#section1">
Esta metaetiqueta significa que el elemento debe estar presente en el DOM, no que se debe cargar el contenido. Por ejemplo, con las imágenes, la sola presencia de la etiqueta <img>
con el id
especificado en el árbol del DOM es suficiente para que la condición se evalúe como verdadera. Es posible que la imagen aún se esté cargando.
Antes de ahondar en el bloqueo de la representación, ten en cuenta que la renderización incremental es un aspecto fundamental de la Web, por lo que debes tener cuidado cuando decidas bloquearla. El impacto de bloquear la renderización debe evaluarse caso por caso. De forma predeterminada, evita usar blocking=render
, a menos que puedas medir y evaluar de forma activa el impacto que tiene en tus usuarios, midiendo el impacto en tus Métricas web esenciales.
Cómo ver los tipos de transición en las transiciones de vista entre documentos
Las transiciones de vista entre documentos también admiten tipos de transición de vista para personalizar las animaciones y los elementos que se capturan.
Por ejemplo, cuando vas a la página siguiente o a la anterior en una paginación, es posible que quieras usar diferentes animaciones en función de si vas a una página superior o una inferior de la secuencia.
Para configurar estos tipos de antemano, agrégalos en la regla de @view-transition
:
@view-transition {
navigation: auto;
types: slide, forwards;
}
Para establecer los tipos sobre la marcha, usa los eventos pageswap
y pagereveal
para manipular el valor 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);
}
});
Los tipos no se transfieren automáticamente del objeto ViewTransition
de la página anterior al objeto ViewTransition
de la página nueva. Debes determinar los tipos que se usarán en, al menos, la página nueva para que las animaciones se ejecuten como se espera.
Para responder a estos tipos, usa el selector de seudoclase :active-view-transition-type()
de la misma manera que con las transiciones de vistas del mismo documento.
/* 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;
}
}
Debido a que los tipos solo se aplican a una transición de vista activa, se limpian automáticamente cuando finaliza una transición de vista. Debido a eso, los tipos funcionan bien con funciones como BFCache.
Demostración
En la siguiente demo de paginación, el contenido de la página se desliza hacia adelante o hacia atrás según el número de página al que te diriges.
El tipo de transición que se debe usar se determina en los eventos pagereveal
y pageswap
a través de las URLs de origen y destino.
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';
}
};
Comentarios
Siempre agradecemos los comentarios de los desarrolladores. Para compartirlas, informa un problema al grupo de trabajo del CSS en GitHub con sugerencias y preguntas. Agrega el prefijo [css-view-transitions]
a tu problema.
Si encuentras un error, informa un error de Chromium.