El problema
Los sitios web suelen proporcionar varias vistas de un objeto, por ejemplo, una lista de productos y, luego, una página de detalles para cada producto. Navegar entre las dos vistas suele parecer desconectado. Cuando un usuario hace clic en la miniatura de un producto para ver sus detalles, la miniatura desaparece y, de inmediato, aparece una imagen nueva y más grande en otra parte de la pantalla. Esta falta de continuidad dificulta que los usuarios sigan las relaciones entre los elementos.
La solución
La API de View Transitions te permite especificar pares de elementos que existen en diferentes estados antes y después de una transición. Cuando se activa una transición con document.startViewTransition() en una navegación de página única (SPA), el navegador identifica estos elementos compartidos por su view-transition-name único compartido. Luego, calcula automáticamente la diferencia en su posición, tamaño y estilo, y los anima de forma fluida del estado anterior al nuevo. Esta transición se produce en la capa superior, incluso por encima de los elementos con valores de z-index altos.
Guía de implementación
Paso 1: Une los cambios de estado en startViewTransition
Para aplicaciones de una sola página (SPA) o cambios de estado simples, une la lógica que actualiza el DOM en document.startViewTransition. El navegador captura una instantánea del estado actual, ejecuta la actualización y, luego, captura el estado nuevo.
function navigate(view) {
// MANDATORY: Wrap the update in startViewTransition
document.startViewTransition(() => updateDOM(view));
}
Paso 2: Asigna nombres de transición compartidos
Usa la propiedad de CSS view-transition-name para indicarle al navegador qué elementos se deben transformar. El nombre puede ser cualquiera (excepto none). OBLIGATORIO: No debe haber más de 1 elemento antes y después con un view-transition-name determinado. Si hay 2 o más elementos con un view-transition-name determinado, el DOM se actualizará al estado nuevo de inmediato, sin una transición.
Puedes usar varios elementos view-transition-name para transformar varios pares de elementos. Por ejemplo, es posible que quieras realizar la transición de la imagen y el título del producto con transiciones independientes.
Como hay varios elementos en la vista de lista, no puedes darles a todos el mismo view-transition-name. Esto se puede resolver de dos maneras en una SPA.
Página de detalles dinámica: Asigna a cada elemento de la página de lista un
view-transition-nameúnico y, luego, aplica ese nombre de forma dinámica al elemento coincidente en la página de detalles cuando se seleccione el elemento de la lista, como se muestra aquí./* In the list view, give each */ #product-1 { view-transition-name: p1 } #product-2 { view-transition-name: p2 } #product-3 { view-transition-name: p3 }function updateDOM(clickedTransitionName){ const hero = document.getElementById("hero"); hero.style.viewTransitionName = clickedTransitionName; }Elemento de lista dinámico: Asigna un
view-transition-nameal elemento de la página de detalles y aplica ese nombre al elemento de la página de lista cuando se seleccione. Quita elview-transition-namedel elemento en la página de la lista cuando regreses a ella.
El elemento #hero de la página de detalles y el elemento .thumbnail seleccionado de la página de lista comparten un view-transition-name.
#hero{
view-transition-name: hero;
}
.thumbnail.selected {
view-transition-name: hero;
}
Cuando se hace clic en una miniatura, debemos preparar la vista de lista asignando el view-transition-name con el selector de clase .selected y realizando los cambios necesarios en el DOM antes de iniciar la transición.
Luego, puedes llamar a document.startViewTransition() y aplicar los cambios para que la página pase de la vista de detalles a la de lista.
Después de volver a la vista de lista, debes limpiar las clases de transición de vista para evitar que la próxima navegación genere un error. Puedes realizar esta limpieza después de que se resuelva la promesa finished de la transición.
// Function called when a thumbnail is clicked
function goFromListToDetail(e){
e.currentTarget.classList.add("selected");
const hero = document.getElementById("hero");
const bgColor = getComputedStyle(e.currentTarget).backgroundColor;
hero.style.background = bgColor;
// Trigger the transition, checking for support
if (!document.startViewTransition) {
document.body.classList.add("detail");
// MANDATORY Accessibility Routing: Route focus to the newly revealed heading to announce context and preserve logical tab flow
document.getElementById("detail-heading")?.focus();
return; // MANDATORY: End function execution if view transitions are not supported.
}
const transition = document.startViewTransition(() => {
document.body.classList.add("detail");
});
// MANDATORY Accessibility Routing: Route focus after the view transition resolves
transition.finished.finally(() => {
document.getElementById("detail-heading")?.focus();
});
}
// Function called when navigating from detail back to list view
function goFromDetailToList() {
if (!document.startViewTransition) {
document.body.classList.remove("detail");
document.getElementById("list-heading")?.focus();
return;
}
const transition = document.startViewTransition(() => {
document.body.classList.remove("detail");
});
// Clean up the list view and route focus
transition.finished.finally(() => {
// Route focus back to list view
document.getElementById("list-heading")?.focus();
// Remove selected classList to remove view-transition-names
document.querySelectorAll(".selected").forEach(
(element) => {
element.classList.remove("selected");
},
);
});
}
El método que elijas dependerá del caso de uso. El elemento de lista dinámico requiere menos CSS repetido, pero más limpieza manual de JavaScript.
Paso 3: Corrige el "estiramiento" de la relación de aspecto
De forma predeterminada, el navegador realiza un fundido cruzado de las instantáneas antiguas y nuevas dentro de un grupo que se estira para ajustarse a ambas. Si estás realizando la transición de texto, establece el ancho del elemento de texto en fit-content tanto en la vista anterior como en la nueva, de modo que la relación de aspecto del elemento de transición sea estable.
#list-page .title {
width: fit-content;
}
#detail-page #title {
width: fit-content;
}
Si estás realizando la transición de elementos que cambian la relación de aspecto, es posible que debas establecer la altura de los pseudoelementos antiguos y nuevos en el 100% del pseudoelemento ::view-transition-pair().
::view-transition-old(hero),
::view-transition-new(hero){
height: 100%;
}
Los seudoelementos son instantáneas de los elementos activos, por lo que también puedes usar las declaraciones object-fit y object-position para tener más control del efecto de transición.
Prácticas recomendadas
- NO especifiques demasiadas transiciones. Usa elementos compartidos solo para el contenido principal que el usuario sigue de forma activa (p.ej., imágenes hero, encabezados).
- SÍ quita los valores temporales de
view-transition-namedespués de que finalice la transición para evitar efectos secundarios en transiciones futuras. - NO realices transiciones de elementos con animaciones activas. Las transiciones de vistas operan en instantáneas, por lo que las animaciones parecerán pausadas durante la transición de vistas.
- SÍ respeta las preferencias del usuario de reducir el movimiento con la consulta de medios
prefers-reduced-motion. - Enrutamiento de accesibilidad OBLIGATORIO: Las transiciones de vistas transforman los diseños de página de forma dinámica, pero no administran el enfoque programático. Si el foco permanece en un elemento que se oculta o se quita durante la transición, se abandona el foco, lo que deja sin contexto a los usuarios de teclados y tecnologías de accesibilidad. Cambia el enfoque de forma programática a un encabezado de página o contenedor de vista actualizado (con
tabindex="-1") inmediatamente después de que se actualice el DOM o cuando se resuelva la promesafinishedde la transición de vista.
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
Estrategias de resguardo
La API de View Transitions está diseñada para la mejora progresiva. Los navegadores que no lo admitan simplemente ejecutarán la actualización del DOM de inmediato sin animación.
function navigate(){
if (!document.startViewTransition) {
// Fallback: Just update the DOM
updateDOM();
} else {
document.startViewTransition(() => updateDOM());
}
}