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

Cuando se produce una transición de vista entre dos documentos diferentes, se la denomina transición de vista entre documentos. Este suele ser el caso en las aplicaciones de varias páginas (MPA). A partir de Chrome 126, se admiten las transiciones de vistas entre documentos.

Navegadores compatibles

  • 126
  • 126
  • x
  • x

Origen

Las transiciones de vista entre documentos se basan en los mismos componentes básicos y principios que las transiciones de vista en el mismo documento, lo cual 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 generan con animaciones de CSS.

La diferencia respecto de las transiciones de vistas de un mismo documento es que, con las transiciones de vistas entre documentos, no es necesario llamar a document.startViewTransition para iniciar una transición de vista. En cambio, las transiciones de vistas entre documentos se activan mediante una navegación de mismo origen de una página a otra, una acción que, por lo general, la realiza el usuario de tu sitio web cuando hace clic en un vínculo.

En otras palabras, no hay una API a la que llamar para iniciar una transición de vistas entre dos documentos. Sin embargo, deben cumplirse dos condiciones:

  • Ambos documentos deben existir en el mismo origen.
  • Debes habilitar las dos páginas para permitir la transición de vistas.

Ambas condiciones se explican más adelante en este documento.


Las transiciones de vistas entre documentos se limitan a las navegaciones del mismo origen

Las transiciones de vistas entre documentos se limitan a las navegaciones del mismo origen. Se considera que una navegación tiene el mismo origen si el origen de las dos 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.

Una URL de ejemplo con el esquema, el nombre de host y el puerto destacados. Juntos, forman el origen.
Una URL de ejemplo con el esquema, el nombre de host y el puerto destacados. Juntos, 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 esos orígenes son del mismo origen. No puedes realizar esa transición cuando navegas de developer.chrome.com a www.chrome.com, ya que son de origen cruzado y del mismo sitio.


Habilita las transiciones de vistas entre documentos

Para que se pueda hacer una transición de vista de varios documentos entre dos documentos, debes habilitar la opción en ambas páginas participantes. Esto se hace con la regla at @view-transition en CSS.

En la regla at de @view-transition, establece el descriptor navigation en auto para habilitar las transiciones de vistas para navegaciones del mismo origen entre documentos.

@view-transition {
  navigation: auto;
}

Si configuras el descriptor navigation como auto, aceptas que se produzcan transiciones de vistas para los siguientes NavigationType:

  • traverse
  • push o replace, si el usuario no inició la activación mediante mecanismos de la IU del navegador

Las navegaciones excluidas de auto incluyen, por ejemplo, navegar con la barra de direcciones de la URL o hacer clic en un favorito, así como cualquier forma de recarga iniciada por el usuario o la secuencia de comandos.

Si la navegación tarda demasiado (más de cuatro segundos en el caso de Chrome), se omite la transición de vista con un DOMException de TimeoutError.

Demostración de las transiciones de vistas entre documentos

Echa un vistazo a 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 vistas se activan cuando se navega de una página a otra.

Grabación de la demostración de Stack Navigator. Requiere Chrome 126 o versiones posteriores.

Cómo personalizar las transiciones de vistas entre documentos

Para personalizar las transiciones de vistas 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, pero están diseñadas para usarse en conjunto.

Los eventos pageswap y pagereveal

Navegadores compatibles

  • 124
  • 124
  • x
  • x

Origen

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 una transición de vista está por suceder 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 marco de una página. Puedes usar esta opción para hacer algunos cambios de último minuto en la página de salida, 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 esta función, 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 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 realmente.

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 de pageswap y pagereveal son dos objetos diferentes. También manejan las distintas promesas de manera diferente:

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

Navegadores compatibles

  • 123
  • 123
  • x
  • x

Origen

En los eventos pageswap y pagereveal, también puedes tomar medidas en función de las URLs de la página anterior y la nueva.

Por ejemplo, en MPA Stack Navigator, el tipo de animación que se use depende de la ruta de navegación:

  • Cuando navegas desde la página de resumen hasta la página de detalles, el contenido nuevo debe deslizarse de derecha a izquierda.
  • Cuando navegas desde la página de detalles a la de resumen, el contenido antiguo debe deslizarse de izquierda a derecha.

Para ello, necesitas información sobre la navegación que, en el caso de pageswap, está por suceder o, en el caso de pagereveal, que acaba de suceder.

Para ello, ahora los navegadores 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 actuales y las 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 él a través de e.activation.

Consulta esta demostración de perfiles, que utiliza 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 vistas.

De esa manera, no tienes que decorar todos los elementos de la lista con un view-transition-name por adelantado. En cambio, esto sucede justo a tiempo con JavaScript, solo en elementos que lo necesiten.

Grabación de la demostración de perfiles. Se requiere Chrome 126 o versiones posteriores.

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 después de sí mismo quitando los valores view-transition-name después de que se ejecuta la transición de vista. De esta manera, la página estará lista para navegaciones sucesivas y también podrá manejar el recorrido del historial.

Para ayudar con esto, usa esta función de utilidad que establece view-transition-name de manera 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 = '';
  }
}

Ahora se puede simplificar el código anterior 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

  • 124
  • 124
  • x
  • x

Origen

En algunos casos, es posible que quieras postergar la primera representación de una página hasta que un elemento determinado esté presente en el nuevo DOM. De esta manera, se evitan flashes y se garantiza que el estado al que estás realizando la animación sea estable.

En <head>, define uno o más IDs de elementos que deben estar presentes antes de que la página obtenga su primera renderización. Para ello, usa la siguiente metaetiqueta.

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

Esta metaetiqueta indica que el elemento debe estar presente en el DOM, no que el contenido debe cargarse. Por ejemplo, con 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 evalúe como verdadera. Es posible que la imagen aún se esté cargando.

Antes de realizar el bloqueo de la representación, ten en cuenta que el procesamiento incremental es un aspecto fundamental de la Web, por lo que debes tener cuidado al bloquear la representación. El impacto del bloqueo de la renderización se debe evaluar caso por caso. De forma predeterminada, evita el uso de blocking=render, a menos que puedas medir y medir de forma activa el impacto que tiene en tus usuarios midiendo el impacto en las Métricas web esenciales.


Cómo ver los tipos de transición en transiciones de vista entre documentos

Las transiciones de vistas entre documentos también admiten tipos de transición de vista para personalizar las animaciones y qué elementos se capturan.

Por ejemplo, cuando vayas 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 a una más baja de la secuencia.

Para configurar estos tipos por adelantado, agrega los tipos en la regla at @view-transition:

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

Para configurar 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 nueva página 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 vista 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, los tipos se borran automáticamente cuando finaliza una transición de vista. Por lo tanto, 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 a la que navegas.

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

El tipo de transición que se usará se determina en los eventos pagereveal y pageswap mediante la observación de las URLs hacia y desde las URLs.

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

Los comentarios de los desarrolladores son siempre bienvenidos. Para compartir un problema, informa 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 en su lugar.