Transiciones de vista entre documentos para aplicaciones de varias páginas

Cuando ocurre una transición de vista entre dos documentos diferentes, se denomina transición de vista entre 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.

Browser Support

  • Chrome: 126.
  • Edge: 126.
  • Firefox: not supported.
  • Safari: 18.2.

Source

Las transiciones de vista entre documentos se basan en los mismos principios y elementos básicos que las transiciones de vista dentro del mismo documento, lo que es muy intencional:

  1. El navegador toma instantáneas de los elementos que tienen un view-transition-name único en la página anterior y en la nueva.
  2. El DOM se actualiza mientras se suprime la renderización.
  3. Por último, las transiciones se ejecutan con animaciones CSS.

La diferencia con las transiciones de vista dentro 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 entre 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 que se pueda 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 habilitar la transición de vista.

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. Se considera que una navegación es 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 utilizados, como se detalla en web.dev.

Ejemplo de una URL con el esquema, el nombre de host y el puerto destacados. En conjunto, forman el origen.
Ejemplo de una URL con el esquema, el nombre de host y el puerto destacados. En conjunto, forman el origen.

Por ejemplo, puedes tener una transición de vista entre documentos cuando navegas de developer.chrome.com a developer.chrome.com/blog, ya que tienen el mismo origen. No puedes tener 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 que abarque dos documentos, ambas páginas participantes deben habilitar esta opción. Esto se hace con la regla @ @view-transition en CSS.

En la regla @ @view-transition, establece el descriptor navigation en auto para habilitar las transiciones de vista para las navegaciones de origen cruzado y mismo origen.

@view-transition {
  navigation: auto;
}

Si configuras el descriptor navigation como auto, habilitas las transiciones de vista para los siguientes NavigationTypes:

  • traverse
  • push o replace, si el usuario no inició la activación a través de los mecanismos de la IU del navegador.

Por ejemplo, las navegaciones excluidas de auto son las que se realizan con la barra de direcciones de la URL o haciendo clic en un marcador, así como cualquier tipo de recarga iniciada por el usuario o una 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. Aquí no hay llamadas a document.startViewTransition(), las transiciones de vista se activan cuando se navega de una página a otra.

Grabación de la demostración de Stack Navigator. Se requiere Chrome 126 o una versión posterior.

Personaliza las transiciones de vista entre documentos

Para personalizar las transiciones de vista entre documentos, puedes usar algunas funciones de la plataforma web.

Estas funciones no forman parte de la especificación de la API de View Transition en sí, sino que están diseñadas para usarse en conjunto con ella.

Los eventos pageswap y pagereveal

Browser Support

  • Chrome: 124.
  • Edge: 124.
  • Firefox: not supported.
  • Safari: 18.2.

Source

Para permitirte personalizar las transiciones de vista 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 ocurrir 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 fotograma de una página. Puedes usar esta función para realizar algunos cambios de último momento en la página saliente, justo antes de que se tomen las instantáneas antiguas.
  • El evento pagereveal se activa en una página después de que se inicializa o reactiva, pero antes de la primera oportunidad de procesamiento. Con ella, puedes personalizar la página nueva antes de que se tomen las nuevas instantáneas.

Por ejemplo, puedes usar estos eventos para establecer o cambiar rápidamente algunos valores de view-transition-name, o bien 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 controlan las diferentes promesas de manera diferente:

  • pageswap: Una vez que se oculta el documento, se omite el objeto ViewTransition anterior. Cuando eso sucede, viewTransition.ready rechaza y viewTransition.finished resuelve.
  • pagereveal: La promesa de updateCallBack ya se resolvió en este punto. Puedes usar las promesas viewTransition.ready y viewTransition.finished.

Browser Support

  • Chrome: 123.
  • Edge: 123.
  • Firefox: 147.
  • Safari: 26.2.

Source

En los eventos pageswap y pagereveal, también puedes tomar medidas en función de las URLs de las páginas antiguas y nuevas.

Por ejemplo, en el navegador de pila de MPA, el tipo de animación que se usará depende de la ruta de navegación:

  • Cuando se navega de la página de resumen a una página de detalles, el contenido nuevo debe deslizarse de derecha a izquierda.
  • Cuando se navega de la página de detalles a la 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 del mismo origen. Este objeto expone el tipo de navegación utilizado, las entradas del historial de destino actual y final, tal como se encuentran 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.

Grabación de la demostración de Profiles. Se requiere Chrome 126 o una versión posterior.

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 se limpia a sí mismo quitando los valores de 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-names 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

Browser Support

  • Chrome: 124.
  • Edge: 124.
  • Firefox: not supported.
  • Safari: not supported.

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 el parpadeo y garantiza que el estado al que animas sea estable.

En <head>, define uno o más IDs de elementos que deben estar presentes antes de que la página se renderice por primera vez con la siguiente metaetiqueta.

<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, en el caso de las imágenes, la mera 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 bloquear la renderización por completo, ten en cuenta que la renderización incremental es un aspecto fundamental de la Web, por lo que debes tener cuidado cuando optes por bloquear la renderización. El impacto del bloqueo de 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.


Tipos de transiciones de vista en 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 anterior en una paginación, es posible que quieras usar diferentes animaciones según si vas a una página superior o inferior de la secuencia.

Para establecer estos tipos por adelantado, agrega los tipos en la regla @ @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 según lo previsto.

Para responder a estos tipos, usa el seudo selector de clase :active-view-transition-type() de la misma manera que con las transiciones de vista en el 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;
  }
}

Dado que los tipos solo se aplican a una transición de vista activa, se limpian automáticamente cuando finaliza una transición de vista. Por eso, los tipos funcionan bien con funciones como BFCache.

Demostración

En la siguiente demostración 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 navegas.

Grabación de la demostración de paginación (MPA). Usa diferentes transiciones según la página a la que vayas.

El tipo de transición que se usará se determina en los eventos pagereveal y pageswap observando las URLs de destino y de origen.

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 compartir tus comentarios, informa un problema al Grupo de trabajo de 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.