Tema
Modales y transiciones de vista
Abrir un modal, cambiar de pestaña o pasar de una lista a un detalle son cambios de contexto. Una buena transición ayuda al usuario a entender de dónde viene y a dónde va; sin ella, el cambio se siente brusco y desorientador.
Concepto: animar elementos que aparecen y desaparecen
El gran problema histórico: no se puede hacer transition desde display: none. El elemento no existe en el layout, así que no hay "estado inicial" desde el que animar. Hoy tenemos tres soluciones:
opacity+visibilityen lugar dedisplay(funciona en todos lados; lo usamos en las pestañas).@starting-style+transition-behavior: allow-discrete: CSS moderno que define el estado inicial de un elemento al aparecer y permite animardisplay(lo usamos en los modales).- View Transitions API: el navegador toma una "foto" antes y después de un cambio del DOM y anima entre ambas automáticamente.
css
dialog {
opacity: 0; transform: scale(.8);
transition: opacity .35s, transform .35s,
overlay .35s allow-discrete, display .35s allow-discrete;
}
dialog[open] { opacity: 1; transform: none; }
/* Estado del que parte la animación al ABRIR */
@starting-style {
dialog[open] { opacity: 0; transform: scale(.8); }
}Si el navegador no lo soporta
Sin @starting-style, el modal simplemente aparece sin animación de entrada: sigue funcionando perfectamente. Es mejora progresiva pura.
Modales con zoom, slide y panel lateral
Tres variantes con el elemento nativo <dialog>: zoom, deslizar desde abajo y drawer lateral. Todas con fondo desenfocado.
¿Por qué <dialog> y no un <div>? Con showModal() obtienes gratis:
- El modal queda encima de todo (top layer), sin pelear con
z-index. - El foco queda atrapado dentro y vuelve al botón al cerrar.
- La tecla Esc lo cierra.
- El fondo es el pseudo-elemento
::backdrop, que también se puede animar.
Lo único que añadimos con JS es cerrar al hacer clic fuera de la caja, comparando las coordenadas del clic con getBoundingClientRect().
Duración de los modales
Entre 200 y 350 ms. Que la salida sea un poco más rápida que la entrada hace que la interfaz se sienta más ágil.
Transición entre vistas (pestañas)
La vista nueva entra desde el lado hacia el que navegas y la anterior sale por el contrario. El indicador inferior se desliza bajo la pestaña activa.
Técnicas usadas:
- Las vistas se apilan en la misma celda de un grid (
grid-area: 1 / 1) para poder hacer crossfade sinposition: absolute. El contenedor toma la altura de la vista más alta. - La dirección se guarda en
--dir(1 o -1) y el CSS calculatranslateX(calc(var(--dir) * 40px)). - El indicador usa
translateX(calc(var(--activo) * 100%)). - Los atributos
role="tab",aria-selectedyrole="tabpanel"hacen las pestañas accesibles.
View Transitions API
La forma más moderna (y con menos código) de animar cambios de vista. Haz clic en un producto: la imagen y el título "vuelan" desde la tarjeta hasta su posición en el detalle.
La API funciona en tres pasos:
js
// 1. Envuelve el cambio del DOM en startViewTransition
function transicion(cambio) {
if (!document.startViewTransition) return cambio(); // fallback: sin animación
document.startViewTransition(cambio);
}
// 2. Da el MISMO view-transition-name al elemento antes y después del cambio
tarjeta.querySelector('.foto').style.viewTransitionName = 'foto-activa';
transicion(() => {
// 3. Haz el cambio: ocultar lista, mostrar detalle, mover el nombre al nuevo elemento
tarjeta.querySelector('.foto').style.viewTransitionName = '';
fotoDetalle.style.viewTransitionName = 'foto-activa';
lista.hidden = true;
detalle.hidden = false;
});Y se personaliza con pseudo-elementos CSS:
css
::view-transition-old(root),
::view-transition-new(root) { animation-duration: .4s; } /* crossfade de toda la página */
::view-transition-group(foto-activa) { animation-duration: .5s; } /* el elemento que "vuela" */Error común: nombres duplicados
Cada view-transition-name debe ser único en la página en el momento de la transición. Si dos elementos lo comparten, el navegador cancela la animación. Por eso el demo limpia los nombres anteriores antes de asignar uno nuevo.
Transiciones entre páginas distintas (MPA)
Para sitios con varias páginas HTML (sin JavaScript de por medio) basta con añadir en el CSS de ambas páginas:
css
@view-transition { navigation: auto; }El navegador hará un crossfade al navegar entre páginas del mismo dominio. Soportado en Chrome/Edge y Safari recientes; en el resto simplemente navega normal.
Ejercicios
- Crea una variante de modal que entre girando ligeramente (
rotate(-3deg) scale(.9)). - Haz que las pestañas también puedan cambiarse con las flechas ← → del teclado.
- En el demo de View Transitions, haz que el precio también "vuele" de la tarjeta al detalle.
Ver solución del ejercicio 2
js
document.querySelector('[role="tablist"]').addEventListener('keydown', (e) => {
if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft') return;
const siguiente = (actual + (e.key === 'ArrowRight' ? 1 : -1) + tabs.length) % tabs.length;
tabs[siguiente].click();
tabs[siguiente].focus();
});Resumen
display: noneno se puede animar contransitionclásica: usaopacity/visibilityo@starting-style+allow-discrete.<dialog>+showModal()= modal accesible (foco, Esc, top layer) con muy poco código.- Pestañas: apila vistas en la misma celda de grid y usa una variable de dirección.
- View Transitions:
startViewTransition()+view-transition-nameúnico, siempre con fallback.