Diseño de movimiento con accesibilidad primero: scroll, parallax y microanimaciones sin marear a la gente
El movimiento puede aclarar la intención—o sabotear silenciosamente la usabilidad con náuseas, tirones y trampas de teclado. Aquí tienes un manual de accesibilidad primero para usar efectos de scroll, parallax y microanimaciones con niveles de intensidad, barreras de rendimiento y una lista de QA que tu estudio puede adoptar de inmediato.
El movimiento no es un adorno. Es un sistema de UX.
Si tu animación al hacer scroll pierde frames, tu interfaz se siente rota. Si tu parallax es demasiado agresivo, la gente se marea. Si tus transiciones “elegantes” se comen el foco, las personas que usan teclado quedan atrapadas. La verdad incómoda: mucho del movimiento “premium” en la web moderna es hostil por defecto.
Este artículo traduce tendencias experimentales de interacción (energía Awwwards/Codrops) en un manual de accesibilidad concreto y listo para enviar—con prefers-reduced-motion, niveles de intensidad de movimiento y barreras de ingeniería que mantienen tu sitio rápido e inclusivo.
El movimiento es UX—no decoración
El buen movimiento hace tres trabajos:
- Explicar el cambio (¿qué acaba de pasar, a dónde fue?)
- Guiar la atención (¿qué importa ahora?)
- Confirmar la causalidad (tu acción causó este resultado)
El mal movimiento hace lo contrario: compite con el contenido, introduce demora y crea incomodidad física.
Un marco de decisión: cuándo el movimiento ayuda vs. distrae
Antes de animar cualquier cosa, pásala por este conjunto rápido de heurísticas.
El movimiento ayuda cuando…
- Reduce la carga cognitiva: p. ej., un expandir/contraer sutil que muestra jerarquía.
- Mejora el rendimiento percibido: p. ej., skeleton loading o un indicador de progreso ligero.
- Aclara relaciones espaciales: p. ej., una tarjeta se expande a una vista de detalle.
- Da feedback inmediato: p. ej., estados de pulsación de botones, pistas de validación de formularios.
El movimiento distrae (o perjudica) cuando…
- Bloquea la lectura: texto moviéndose mientras la gente intenta escanear.
- Secuestra el scroll: scroll-jacking, animaciones fijadas que pelean con el usuario.
- Crea movimiento continuo de fondo: parallax en bucle, capas de ruido a la deriva.
- Sorprende a los usuarios: zooms repentinos, oscilación rápida, movimiento tipo cámara.
Regla general: Si el movimiento no mejora la comprensión o el feedback, probablemente es decoración—y la decoración debería ser lo primero que se reduzca por accesibilidad y rendimiento.
La mentalidad de “presupuesto de movimiento”
Los estudios ya usan presupuestos de rendimiento (KB, LCP, CLS). Aplica la misma disciplina al movimiento:
- Duración máxima para feedback de UI: ~150–250ms (microinteracciones)
- Duración máxima para transiciones: ~200–500ms (transiciones de página/ruta)
- Distancia máxima para cambios de UI: mantenla pequeña; evita grandes recorridos en controles esenciales
- Máximo de animaciones simultáneas: menos es mejor; prioriza una animación focal a la vez
La lista de accesibilidad para animación
El diseño de movimiento con accesibilidad primero no es solo “añadir prefers-reduced-motion”. Es un conjunto de valores por defecto.
1) Respeta prefers-reduced-motion (y no castigues a los usuarios por ello)
Las personas que activan reducción de movimiento no deberían recibir una experiencia rota o una UI “inferior”. Deberían recibir una experiencia estable, legible y totalmente funcional.
Enfoque base:
- Por defecto: movimiento con buen gusto
- Movimiento reducido: elimina movimiento no esencial, conserva cambios de estado esenciales
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
scroll-behavior: auto !important;
}
}
Este reset contundente es una buena base, pero no es toda la solución. Aún necesitas fallbacks intencionales para narrativa guiada por scroll y parallax.
2) Crea “niveles de intensidad” de movimiento (un estándar para todo el estudio)
En lugar de un binario “movimiento on/off”, define niveles que tu equipo pueda implementar de forma consistente.
Un sistema de niveles práctico:
- Nivel 0 (Reducir): sin parallax, sin transforms vinculados al scroll, sin video en autoplay; cambios de estado instantáneos
- Nivel 1 (Sutil): fades de opacidad, transforms pequeños (<8px), duraciones cortas; sin movimiento continuo
- Nivel 2 (Expresivo): transiciones más ricas, revelados escalonados, efectos vinculados al scroll limitados
- Nivel 3 (Experimental): storytelling pesado, secciones fijadas, secuencias complejas (usar con moderación)
Patrón de implementación (variables CSS + atributo data):
:root {
--motion-multiplier: 1;
}
html[data-motion="reduce"] { --motion-multiplier: 0; }
html[data-motion="subtle"] { --motion-multiplier: 0.6; }
html[data-motion="expressive"] { --motion-multiplier: 1; }
.card {
transition: transform calc(200ms * var(--motion-multiplier)) ease,
opacity calc(200ms * var(--motion-multiplier)) ease;
}
html[data-motion="reduce"] .card {
transition: none;
}
Luego establece el atributo en JS según la preferencia del sistema, con un toggle opcional del usuario guardado en localStorage.
3) Evita desencadenantes vestibulares
Ciertos patrones de movimiento tienen más probabilidades de causar náuseas o mareos:
- Parallax con grandes diferencias de profundidad (el fondo se mueve mucho más lento/rápido que el primer plano)
- Zoom/escalado de todo el viewport
- Rotación y oscilación
- Movimiento ligado al scroll que no coincide con la sensación del dedo/trackpad
Mitigaciones:
- Mantén pequeños los offsets del parallax (piensa en píxeles de un solo dígito, no derivas de 100px en el hero)
- Prefiere opacidad y translateY sutil en lugar de scale/rotate
- Evita movimiento ambiental continuo detrás del texto
4) Protege los flujos de teclado y tecnologías de asistencia
El movimiento a menudo rompe la accesibilidad de forma indirecta:
- Anillos de foco ocultos por transforms/overlays
- Paneles off-canvas que no atrapan el foco correctamente
- Secciones con scroll-jacking que impiden el scroll normal con teclado
Requisitos base:
- Usa HTML semántico para controles (un
<button>,<a>reales) - Asegura que el foco sea visible en todo momento
- Para overlays/modales: implementa focus trap,
aria-modal, y restaura el foco al cerrar - No animes elementos de forma que el foco se mueva debajo del usuario
Si tu animación cambia el layout, pruébala con Tab/Shift+Tab antes de darla por terminada.
Patrones que funcionan: microinteracciones, transiciones y storytelling
Aún puedes construir experiencias modernas y de alta artesanía—sin hacer del movimiento el costo de entrada.
Microinteracciones: el lugar más seguro para ser expresivo
Las microinteracciones son pequeñas, locales y disparadas por el usuario—ideales para accesibilidad.
Úsalas para:
- Feedback de pulsación/hover en botones
- Validación de formularios (color sutil + icono + pequeño movimiento)
- Confirmación de copiar al portapapeles
- Estados de abrir/cerrar menús
Guías:
- Que sea rápido (150–250ms)
- Que el movimiento sea pequeño (2–8px)
- Acompaña el movimiento con señales no basadas en movimiento (color, texto, icono)
Ejemplo: feedback de botón sin trucos
.button {
transform: translateZ(0);
transition: transform 180ms ease, background-color 180ms ease;
}
.button:active {
transform: translateY(1px);
}
@media (prefers-reduced-motion: reduce) {
.button { transition: background-color 180ms ease; }
.button:active { transform: none; }
}
Transiciones: haz que la navegación se sienta coherente (sin ralentizarla)
Las transiciones de página pueden mejorar la calidad percibida, pero también es donde los equipos se pasan.
Lo que funciona:
- Crossfade + translate ligero en contenedores clave
- Mantén la navegación responsiva; no bloquees la UI detrás de secuencias largas
- Si usas transiciones de ruta (Next.js/React), asegúrate de que el foco vaya al heading de la nueva página
Conclusión concreta:
- Anima una capa (el contenedor de contenido), no todo el DOM.
- Preserva siempre la legibilidad: evita mover párrafos grandes.
Storytelling con scroll: úsalo como un dial, no como una montaña rusa
Las experiencias guiadas por scroll pueden ser geniales para narrativas de producto, historias de datos y portfolios. El modo de fallo es tratar el scroll como una línea de tiempo de video.
Storytelling con scroll con accesibilidad primero:
- Prefiere pasos discretos (snap points / revelados por sección) en lugar de scrubbing continuo
- Mantén el movimiento como secundario al contenido: la historia debería funcionar también como una página estática
- Ofrece una salida: no atrapes a los usuarios en secciones fijadas
Si te inspiran demos de Codrops, trátalas como prototipos de I+D. La versión de producción necesita fallbacks.
Ingeniería: técnicas CSS/JS y presupuestos de rendimiento
La mayoría de los problemas de accesibilidad del movimiento aparecen primero como problemas de rendimiento: tirones, input con retraso, scroll roto.
Barreras de rendimiento (qué estandarizar en tu estudio)
Prefiere propiedades amigables con la GPU
Anima:
transform(translate)opacity
Evita animar:
top/left(layout)width/height(layout)filter(a menudo caro)- cambios grandes de
box-shadow(pintado pesado)
Usa la composición de forma intencional
will-change puede ayudar, pero abusar de ello aumenta el uso de memoria.
- Aplica
will-change: transformsolo a elementos que realmente se animan - Retíralo cuando ya no sea necesario (especialmente en páginas largas)
No ates trabajo pesado a eventos de scroll
El clásico tropiezo: window.addEventListener('scroll', ...) + lecturas/escrituras del DOM en cada frame.
Mejores opciones:
- Usa IntersectionObserver para patrones de revelar-al-entrar
- Usa requestAnimationFrame para transforms vinculados al scroll (y mantén los cálculos diminutos)
- Considera la API emergente de Scroll-Driven Animations (
scroll-timeline) donde esté soportada, con fallbacks
Ejemplo: revelar al entrar (barato y accesible)
const items = document.querySelectorAll('[data-reveal]');
const io = new IntersectionObserver((entries) => {
for (const e of entries) {
if (e.isIntersecting) e.target.classList.add('is-visible');
}
}, { threshold: 0.2 });
items.forEach(el => io.observe(el));
[data-reveal] {
opacity: 0;
transform: translateY(8px);
transition: opacity 240ms ease, transform 240ms ease;
}
[data-reveal].is-visible {
opacity: 1;
transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
[data-reveal] { opacity: 1; transform: none; transition: none; }
}
Parallax sin náuseas (y sin scroll-jank)
Si debes hacer parallax:
- Mantén pequeños los offsets
- Mueve fondos sutilmente, no contenido en primer plano
- Evita parallax en bloques de texto
- Desactívalo con movimiento reducido y, a menudo, en móvil
Consejos de implementación:
- Usa
transform: translate3d(0, y, 0) - Actualiza en
requestAnimationFrame - Cachea mediciones; evita layout thrash (no llames
getBoundingClientRect()repetidamente dentro del mismo frame sin necesidad)
Throttling, debouncing y “hacer menos”
Para efectos vinculados al scroll, el objetivo no es código ingenioso—es menos operaciones.
- Usa
requestAnimationFramecomo tu throttle - Agrupa lecturas y luego escrituras
- Evita disparar bucles de recálculo de estilos
Un patrón simple de throttle con rAF:
let ticking = false;
function onScroll() {
if (!ticking) {
window.requestAnimationFrame(() => {
// compute minimal transforms here
ticking = false;
});
ticking = true;
}
}
window.addEventListener('scroll', onScroll, { passive: true });
Define presupuestos de rendimiento relacionados con el movimiento
El movimiento es donde más se siente el rendimiento.
Barreras listas para estudio:
- Apunta a 60fps para movimiento interactivo (o al menos 30fps estables en dispositivos de gama baja)
- Mantén tareas largas fuera de ventanas de interacción (evita picos de JS durante el scroll)
- Vigila INP (Interaction to Next Paint) junto con LCP/CLS
- Limita el trabajo de animación por frame: si no puedes explicar qué corre en cada frame, es demasiado
Herramientas que los equipos realmente usan:
- Panel Performance de Chrome DevTools (busca tareas largas, layout thrash)
- Lighthouse + Core Web Vitals (INP/LCP/CLS)
- WebPageTest (trazas en dispositivos reales)
- RUM (SpeedCurve, New Relic, Datadog) para detectar tirones en el mundo real
Errores comunes (y cómo evitarlos)
Error 1: Scroll-jank por layout thrashing
Síntomas:
- Scroll a tirones
- Los ventiladores se disparan
- Las animaciones se quedan atrás del dedo/trackpad
Arreglos:
- Deja de animar propiedades de layout
- Evita leer layout y escribir estilos repetidamente en el mismo tick
- Sustituye listeners de scroll por IntersectionObserver cuando sea posible
Error 2: Náuseas por parallax con profundidad excesiva
Síntomas:
- Usuarios reportan mareo
- La página se siente como una cámara en movimiento
Arreglos:
- Reduce la amplitud drásticamente
- Elimina parallax detrás del texto
- Apágalo para
prefers-reduced-motiony, a menudo, para pantallas más pequeñas
Error 3: Trampas de foco/teclado en overlays animados
Síntomas:
- La tecla Tab desaparece en el vacío
- El foco salta detrás de un modal
- ESC no cierra
Arreglos:
- Usa un enfoque de diálogo probado (p. ej., Radix UI Dialog, Headless UI, o
<dialog>nativo con polyfills cuidadosos) - Atrapa el foco mientras está abierto y restaura el foco al cerrar
- No animes elementos enfocables fuera de pantalla sin gestionar el estado del foco
QA: cómo probar el movimiento como un profesional
Probar movimiento no es subjetivo si lo vuelves procedimental.
1) Prueba con movimiento reducido activado
- macOS: System Settings → Accessibility → Display → Reduce motion
- iOS: Accessibility → Motion → Reduce Motion
- Windows: Settings → Accessibility → Visual effects → Animation effects
Verifica:
- Sin parallax vinculado al scroll
- Sin fondos en movimiento con autoplay
- Los flujos principales siguen comunicando estado (abrir/cerrar, éxito/error)
2) Pasada solo con teclado
Checklist:
- ¿Puedes llegar a cada elemento interactivo?
- ¿El foco es siempre visible?
- ¿Los overlays atrapan el foco y lo restauran?
- ¿Las secciones animadas bloquean el scroll normal?
3) Comprobación rápida con lector de pantalla
No necesitas ser experto en lectores de pantalla para detectar problemas comunes.
- VoiceOver (macOS/iOS), NVDA (Windows)
- Confirma que la UI animada no reordena el contenido de forma inesperada
- Asegura que existan anuncios para actualizaciones dinámicas (usa
aria-livecon moderación e intención)
4) Perfilado de rendimiento en un dispositivo “malo”
No valides el movimiento solo en un MacBook al máximo.
- Usa CPU throttling de Chrome
- Prueba en un Android de gama media/baja si tu audiencia lo incluye
- Graba una traza de Performance mientras haces scroll por tu sección más pesada
Si el movimiento solo se siente bien en tu máquina, no está listo para producción.
Conclusión: construye un sistema de movimiento que puedas defender
Los estudios que están haciendo el mejor trabajo ahora mismo no están eligiendo entre “accesibilidad aburrida” y “movimiento emocionante”. Están construyendo un sistema de movimiento: intencional, por niveles, eficiente y respetuoso con las preferencias del usuario.
Tus próximos pasos inmediatos:
- Adopta niveles de intensidad de movimiento e intégralos en tus design tokens/variables CSS.
- Implementa prefers-reduced-motion como una experiencia de primera clase, no como un añadido.
- Estandariza barreras de ingeniería: transform/opacity, throttling con rAF, IntersectionObserver.
- Añade una checklist de QA de movimiento a cada lanzamiento: movimiento reducido, teclado, lector de pantalla, rendimiento en gama baja.
Si quieres, comparte un enlace a una página que estés animando (o un prototipo). Podemos mapear tus animaciones a niveles de intensidad, identificar los desencadenantes vestibulares de mayor riesgo y proponer una variante de movimiento reducido que siga sintiéndose premium.
