Saltar al contenido

CSS, de a poco

Curso de CSS

View Transitions

Lección 32 de 37 · 4 min

Pasar de una vista a otra suele ser un corte seco. Con View Transitions el navegador anima el cambio por vos: le decís qué cambia y él se encarga del movimiento.

Captura, cambio, animación

Una view transition funciona en tres pasos:

  1. Capturafoto viejaEl navegador saca una foto del estado actual de la página.
  2. Cambiotu funciónCorre tu función, que actualiza el DOM.
  3. Animación::view-transitionPone las fotos viejas y la página nueva en una capa por encima, y anima de unas a otra.

Dentro de una misma página, la transición arranca con document.startViewTransition(), que recibe la función que cambia el DOM. Es Baseline recién disponible desde octubre de 2025: anda en las versiones actuales de Chrome, Edge, Safari y Firefox. Donde no existe, cambiá el DOM igual, sin animación:

vista.js
function cambiarVista(actualizar) {
if (!document.startViewTransition) {
actualizar();
return;
}
document.startViewTransition(actualizar);
}

Sin más CSS, la animación por defecto es un fundido (crossfade) de 250ms: la foto vieja se desvanece mientras la nueva aparece. Elegí un estilo, ajustá la duración y tocá una familia tipográfica para abrir su muestra; Volver la cierra. En morph, el «Ag» viaja de su fila a la muestra grande: la línea punteada marca el elemento con nombre propio. El demo además mueve todo con cubic-bezier(0.22, 1, 0.36, 1), una curva que arranca rápido y frena suave.

Estilo de transición
lista de familias · 300ms

Cada sistema resuelve estas palabras clave con sus propias fuentes, así que en otro equipo las muestras pueden verse distintas.

escenario.css
@supports (view-transition-name: none) {
.escenario {
view-transition-name: escenario;
}
::view-transition-group(*) {
animation-duration: 300ms;
animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
}
}
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
escenario.html
<html class="[&::view-transition-group(*)]:[animation-duration:300ms] [&::view-transition-group(*)]:[animation-timing-function:cubic-bezier(0.22,1,0.36,1)] motion-reduce:[&::view-transition-group(*)]:animate-none">
<div class="[view-transition-name:escenario]">
…
</div>
</html>

Nombres y pseudo-elementos

view-transition-name le da a un elemento su propia captura. Si un elemento con el mismo nombre existe antes y después del cambio, el navegador no lo funde: hace un morph, y lo lleva de su posición y tamaño viejos a los nuevos. El nombre tiene que ser único en toda la página; si dos elementos visibles lo comparten, el navegador saltea la transición. Con las capturas arma un árbol de pseudo-elementos que podés estilar como cualquier otro:

  1. La capa::view-transitionCubre toda la página mientras dura la transición.
  2. El grupo::view-transition-group(nombre)Mueve y redimensiona. Acá van la duración y la curva.
  3. El par::view-transition-image-pair(nombre)Junta las dos capturas. Se dibujan sobre toda la página, así que el overflow: hidden del contenedor no las recorta: para que un deslizamiento no se salga, poné overflow: clip acá.
  4. Vieja y nueva::view-transition-old · -newLa captura vieja y la nueva. Cambiá su animación con tus propios @keyframes, como hace el deslizamiento del demo.
El árbol de pseudo-elementos durante el morph: ::view-transition cubre la página; adentro, el grupo familia-1-muestra mueve y redimensiona; adentro del grupo, el par de imágenes recorta con overflow: clip y junta la captura vieja, que se va, con la nueva, que llega.

view-transition-class agrupa varios nombres bajo una clase, así un solo selector los estila a todos. También es Baseline recién disponible desde 2025:

familias.css
.familia-1 .muestra {
view-transition-name: familia-1-muestra;
view-transition-class: familia;
}
::view-transition-group(*.familia) {
animation-duration: 400ms;
}

Toda la página participa de la transición: cada elemento con nombre se captura, cambie o no. Poné los nombres solo en lo que cambia y sacalos cuando la transición termina, como hace el demo.

Entre páginas y con movimiento reducido

En un sitio de varias páginas no hay función que llamar: la página de origen y la de destino optan con @view-transition, y tienen que ser del mismo origen. Su soporte todavía es limitado (Chrome y Edge 126, Safari 18.2, Firefox no), así que usalo como mejora progresiva: los navegadores que no lo entienden ignoran la regla y navegan como siempre.

paginas.css
@view-transition {
navigation: auto;
}

En frameworks, la integración pasa por el componente: Next.js usa el <ViewTransition> de React (incluido en React 19.3), y las navegaciones del App Router lo activan. Por debajo es la misma API, así que los mismos pseudo-elementos sirven para estilarla.

Respetá prefers-reduced-motion: si el sistema pide menos movimiento, cambiá el DOM sin llamar a startViewTransition(), como hace el demo, o apagá las animaciones con CSS:

movimiento.css
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}

En Tailwind

Tailwind v4 no trae utilidades para View Transitions. Los nombres van en propiedades arbitrarias, y como los pseudo-elementos ::view-transition-* cuelgan de la raíz del documento, sus reglas van en <html> con una variante arbitraria.

CSSTailwind
view-transition-name: familia-1-muestra[view-transition-name:familia-1-muestra]
view-transition-class: familia[view-transition-class:familia]
::view-transition-group(*) con animation-duration: 300ms, en <html>[&::view-transition-group(*)]:[animation-duration:300ms]
@media (prefers-reduced-motion: reduce)motion-reduce:[&::view-transition-group(*)]:animate-none
escenario.html
<html class="[&::view-transition-group(*)]:[animation-duration:300ms]
motion-reduce:[&::view-transition-group(*)]:animate-none">
<body>
<div class="[view-transition-name:familia-1-muestra]
[view-transition-class:familia]">…</div>
</body>
</html>