Kodokon kodokon.com

Interacciones modernas: scroll-snap, view transitions

Orquesta el desplazamiento magnético, las transiciones de estado animadas por el navegador y el respeto riguroso a prefers-reduced-motion.

11 min · 3 preguntas

Abrir esta lección en Kodokon

El scroll snap involucra a dos actores: el contenedor define un snapport (su área visible, encogida por scroll-padding) y una estrategia mediante scroll-snap-type - proximity solo ajusta cerca de un punto de anclaje, mientras que mandatory garantiza que siempre aterrizas en uno, incluso tras un desplazamiento programático. Los hijos declaran su área de alineación con scroll-snap-align (ajustable mediante scroll-margin). Una sutileza poco conocida: durante un deslizamiento rápido, el navegador puede legítimamente saltarse varios puntos de anclaje; scroll-snap-stop: always lo obliga a detenerse en cada uno, esencial para un carrusel estilo stories.

CSS
.carousel {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: 1rem;
}
.carousel > article {
  flex: 0 0 80%;
  scroll-snap-align: center;
  scroll-snap-stop: always;
}
Un carrusel magnético: cada diapositiva se centra sola y ningún deslizamiento puede saltarse una.

La API View Transitions anima un cambio de estado del DOM sin ninguna biblioteca. document.startViewTransition(callback) captura una instantánea visual del estado actual, ejecuta tu callback (que muta el DOM) y luego anima entre el estado antiguo y el nuevo. Por dentro, el navegador construye un árbol de pseudoelementos sobre la página: ::view-transition contiene elementos ::view-transition-group, cada uno con un ::view-transition-old (la instantánea, contenido reemplazado comparable a una imagen) y un ::view-transition-new (la representación en vivo). Por defecto: un fundido cruzado. La página es no interactiva durante la transición.

JAVASCRIPT
function applyUpdate(updateDom) {
  if (!document.startViewTransition) {
    updateDom();
    return;
  }
  document.startViewTransition(() => updateDom());
}
Mejora progresiva: sin soporte, la actualización simplemente se aplica sin animación.

El verdadero poder viene de view-transition-name: un elemento nombrado se extrae a su propio grupo de transición. Si existe un elemento con el mismo nombre antes y después, el navegador interpola automáticamente la posición y el tamaño entre ambos - el efecto de morfismo de una miniatura a su vista a pantalla completa, sin una sola línea de JavaScript de animación. Cada nombre debe ser único en la página en cualquier momento dado, o la transición se abandona. Los pseudoelementos se estilizan con animaciones CSS corrientes.

CSS
.hero-thumb { view-transition-name: hero; }

::view-transition-old(hero),
::view-transition-new(hero) {
  animation-duration: 0.4s;
  animation-timing-function: ease-in-out;
}
El grupo hero nombrado es interceptado y se personaliza la duración de su animación.

Queda la obligación de accesibilidad: prefers-reduced-motion: reduce señala a un usuario sensible al movimiento (trastornos vestibulares, migrañas). Reducir no significa eliminar: un fundido de opacidad sigue siendo aceptable; son los grandes movimientos, los zooms y los efectos de parallax los que deben neutralizarse. Ya conoces la sobrescritura global defensiva en CSS (duraciones casi nulas, tratada en el módulo anterior); las view transitions añaden un segundo frente: del lado de JavaScript, consulta window.matchMedia("(prefers-reduced-motion: reduce)") antes de lanzar una transición o un desplazamiento animado.

JAVASCRIPT
const reduceMotion = window.matchMedia(
  '(prefers-reduced-motion: reduce)'
).matches;

if (reduceMotion || !document.startViewTransition) {
  updateDom();
} else {
  document.startViewTransition(() => updateDom());
}
La salvaguarda de JavaScript: la actualización se aplica sin morfismo cuando el usuario pide menos movimiento.

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. ¿Qué hace scroll-snap-stop: always en un elemento de carrusel?
    • Deshabilita la inercia del desplazamiento táctil
    • Fuerza una parada en cada punto de anclaje, incluso durante un deslizamiento rápido
    • Bloquea el desplazamiento a lo largo del eje perpendicular
  2. ¿Qué captura document.startViewTransition() antes de ejecutar tu callback?
    • Un clon profundo del DOM actual
    • Una instantánea visual del estado antiguo, reproducida en ::view-transition-old
    • El HTML serializado de toda la página
  3. ¿Cómo respetas prefers-reduced-motion antes de lanzar una view transition desde JavaScript?
    • No puedes: esta preferencia solo es legible en CSS
    • Comprueba window.matchMedia('(prefers-reduced-motion: reduce)').matches y aplica la actualización sin startViewTransition cuando coincide
    • Pasa la opción { motion: 'reduce' } a startViewTransition