Soporte de movimiento reducido: prefers-reduced-motion bien implementado

¿Te fue útil?

El síntoma en producción: tickets de soporte que dicen cosas como “su sitio me provoca náuseas”, “al desplazar se siente como un terremoto” o “la página de inicio de sesión me está peleando”. Esto no son “preferencias de diseño”. Son errores de fiabilidad para personas.

Si publicas animaciones sin una estrategia real de movimiento reducido, estás jugando a los dados con la accesibilidad, la conversión y la respuesta a incidentes. La buena noticia: prefers-reduced-motion es una de las pocas funcionalidades de la plataforma web que es tanto humana como barata operativamente —si la implementas con disciplina.

Qué es realmente prefers-reduced-motion (y qué no es)

prefers-reduced-motion es una preferencia de usuario expuesta a la plataforma web mediante media queries de CSS y APIs de JavaScript. Es una señal de que el usuario desea menos movimiento. Menos movimiento puede significar menos transiciones, sin parallax, sin animaciones que se reproduzcan automáticamente, sin “secuestrar el desplazamiento” y sin un desplazamiento “suave” que arrastra la página bajo su cursor como si la remolcaran.

No es:

  • Una petición para hacer tu interfaz fea.
  • Una sugerencia para reducir la tasa de frames manteniendo la misma animación (eso puede empeorar la sensación).
  • Un lugar para tirar cualquier fallback que “no queremos depurar”. Movimiento reducido no es “calidad reducida”.

Piensa en el movimiento como una dependencia de producción. Puede fallar. Puede sobrecargar un dispositivo cliente. Y puede dañar a los usuarios. Soportar el movimiento reducido es tu disyuntor.

La realidad operativa: los errores de movimiento son errores de sistemas distribuidos

Las animaciones no se ejecutan en el vacío. Interactúan con:

  • programación CPU/GPU (especialmente en gráficos integrados y modos de bajo consumo)
  • dispositivos de entrada (trackpads, ratones con rueda, táctil)
  • rendimiento de layout y paint (que puedes saturar deliberadamente)
  • ciclo de vida del framework (re-renderizados de React/Angular/Vue frente al estado de animación)
  • widgets de terceros (anuncios, chat, analytics, pruebas A/B)

Cuando el movimiento reducido está activado, tu sistema efectivamente está en un modo de ejecución distinto. Trátalo como tal: pruébalo, móntorealo y evita que derive.

Una cita a la que vuelvo porque aplica directamente aquí: «La esperanza no es una estrategia.» — Gene Kranz

(Tu estrategia de animación no debería ser “esperar que los usuarios no lo noten”. Lo notan.)

Contexto histórico y datos interesantes

  • Los ajustes de “reducir movimiento” a nivel de SO preceden a la función web. Las plataformas añadieron reducción de movimiento mayormente por accesibilidad y confort vestibular; la web ganó después un hook estandarizado.
  • prefers-reduced-motion es una característica de Media Queries Level 5. Forma parte de la ola moderna de señales de “preferencia del usuario” junto a esquema claro/oscuro.
  • La señal suele ser binaria, pero la implementación varía. Normalmente verás reduce vs no-preference; algunos entornos pueden comportarse distinto en contextos embebidos.
  • El “scroll suave” se volvió por defecto sorprendentemente rápido. A los diseñadores les encantó; a algunos usuarios les provocó malestar. La solución a nivel de especificación no fue “prohibirlo” sino “respetar la preferencia”.
  • Los trastornos vestibulares no son casos raros. La sensibilidad al movimiento puede venir por migrañas, afecciones del oído interno, concusiones, medicamentos o simplemente envejecimiento. Tu base de usuarios no es un demo de entusiastas de motion graphics de 25 años.
  • El parallax es un ofensor frecuente. Acopla la entrada de desplazamiento al movimiento por capas; para algunos usuarios parece que el mundo se desliza bajo ellos.
  • Las animaciones que se reproducen automáticamente suelen ser peores que las transiciones. Pueden ser incesantes, sobre todo cuando se repiten en la visión periférica.
  • Rendimiento y accesibilidad están entrelazados. Una animación con tirones (frames perdidos) puede desencadenar incomodidad más que un movimiento suave, así que “simplemente dejar que se trabe” no es una solución.
  • El movimiento reducido puede mejorar métricas de negocio. No porque el movimiento sea “malo”, sino porque eliminar distracciones y náuseas tiende a reducir rebotes y clics de rabia.

Broma #1: Las animaciones son como becarios—geniales cuando están supervisadas, catastróficas cuando se les deja “expresarse” en producción.

Criterio de finalización: movimiento reducido que no retrocede

La mayoría de equipos “soportan movimiento reducido” espolvoreando un par de reglas CSS en un rincón y listo. Eso es como añadir una regla de firewall y declarar la empresa segura.

Aquí tienes una definición práctica de terminado que sobrevive rediseños y migraciones de framework:

1) Tienes una única fuente de verdad para la preferencia

CSS puede leerla con media queries, JS puede leerla con matchMedia. Pero tu app no debería tener cinco utilidades distintas “isReducedMotion” que discrepen entre sí.

2) Has clasificado el movimiento, no solo lo apagaste

No todo movimiento es igual. Clasifica:

  • Esencial: comunica cambios de estado (por ejemplo, anillo de foco, cambios sutiles de opacidad) y puede acortarse en lugar de eliminarse.
  • Útil: mejora la comprensión (por ejemplo, una breve transición de expandir/colapsar). En modo reduce: acortar, quitar overshoot, eliminar rebotes.
  • Decorativo: bucles de fondo, confeti, parallax, tarjetas “respirando”. En modo reduce: desactivar.

3) El movimiento reducido funciona sin JS

Empieza con CSS y luego refina con JS. Si JS falla, el modo reducido debería seguir siendo respetado para la mayoría de efectos.

4) Las configuraciones de movimiento se prueban como una variante de primera clase

No “alguien lo comprobó una vez en un MacBook”. Quieres:

  • cobertura de regresión visual para el modo reducido
  • tests unitarios alrededor de tus utilidades de motion
  • un paso explícito de QA en la aprobación de la release

5) Las fuentes de animación de terceros están controladas

Lottie, embeds de marketing, widgets de chat, overlays de analytics—estos son los culpables habituales. Necesitas una política: o respetan el movimiento reducido, o no se publican.

6) Puedes explicar qué ocurre al alternar

Los usuarios pueden cambiar el ajuste del SO mientras tu app está abierta. Tu código debe responder. Si tus animaciones solo comprueban al inicio, has construido una característica que “funciona en demos”.

Guía rápida de diagnóstico

Estás de guardia (o finges que no lo estás). Llega un ticket: “Movimiento reducido activado, pero el sitio aún anima.” O peor: “Movimiento reducido activado y el sitio ahora está roto.” Aquí tienes cómo encontrar el cuello de botella rápido.

Primero: confirma que la preferencia sea realmente “reduce” en el entorno

  • ¿Está habilitado el ajuste del SO?
  • ¿El navegador lo expone (no en algún modo webview raro)?
  • ¿Tu código lo está leyendo correctamente?

Segundo: identifica de dónde viene el movimiento

  • transiciones/animaciones CSS
  • animaciones impulsadas por JS (bucles requestAnimationFrame)
  • comportamiento de scroll (scroll suave de CSS o librerías JS de scroll)
  • bucle canvas/webgl
  • iframes embebidos o scripts de terceros

Tercero: comprueba si “reduce” está implementado como “desactivar” o “acortar”

Si colocas duraciones a 0ms globalmente, puedes crear nuevos fallos: saltos de foco, thrash de layout, problemas de temporización de eventos, listeners de “animationend” que nunca se ejecutan, etc.

Cuarto: verifica actualizaciones en tiempo de ejecución

Alterna el ajuste del SO mientras la app está abierta. Si nada cambia, probablemente tengas un valor obsoleto cacheado en la carga del módulo.

Quinto: busca regresiones introducidas por optimizaciones

Ofensores comunes: “mejoras de rendimiento” que movieron la lógica de animación a una utilidad compartida que ya no lee la preferencia, o refactors de CSS que reemplazaron propiedades seguras por otras que disparan layout.

Patrones de implementación: CSS, JS y sistemas de componentes

CSS: la base que siempre debes tener

Empieza con una política que se aplique a toda la app. Un baseline típico:

  • desactivar animaciones decorativas de larga duración
  • reducir o eliminar transiciones (especialmente movimientos basados en transform)
  • desactivar el scroll suave

El mecanismo CSS es directo:

  • @media (prefers-reduced-motion: reduce) para el modo reduce
  • @media (prefers-reduced-motion: no-preference) para el modo por defecto

Pero “directo” es donde nacen los bugs. Necesitas saber qué desactivar, y dónde las reglas globales hacen más daño:

La trampa del reset global

Has visto este snippet:

  • setear animation-duration: 0.001ms !important
  • setear transition-duration: 0.001ms !important

Es popular porque es fácil. También es peligroso porque:

  • rompe componentes que dependen del timing de transiciones para limpieza de estado.
  • puede causar saltos repentinos que se sienten peor que un fade corto.
  • oculta fuentes de movimiento en desarrollo porque todo “más o menos funciona”.

Haz un enfoque dirigido: define tokens de motion (duraciones/easings) y cámbialos según la preferencia. Desactiva solo los bucles decorativos verdaderamente globales.

JS: lee la preferencia una vez, luego escucha cambios

Usa window.matchMedia('(prefers-reduced-motion: reduce)'). Pero no lo hagas en diez archivos. Envuélvelo.

Detalle importante: los navegadores han cambiado APIs con el tiempo; algunos entornos soportan addEventListener('change', ...), otros antiguos usan addListener. Tu wrapper debería manejar ambos.

Frameworks de componentes: evita “motion como efecto secundario”

En React y afines, el movimiento suele volverse un efecto secundario: un hook dispara una transición al montar, una librería anima cambios de layout, una “micro-interacción” se activa al hover.

Dos directrices que te mantienen fuera de problemas:

  1. Haz parámetros de motion props explícitas. Duración, easing y si el movimiento está habilitado no deberían estar hardcodeados en lo profundo de un componente.
  2. Nunca infieras movimiento reducido desde “rendimiento del dispositivo”. Los usuarios piden menos movimiento por confort, no porque su GPU esté cansada.

Scroll y navegación: trata el scroll suave como una herramienta potente

CSS scroll-behavior: smooth es tentador porque es una línea. También es una de las maneras más fáciles de violar el movimiento reducido.

Política:

  • Por defecto: permitir scroll suave solo para navegación explícita iniciada por el usuario (por ejemplo, clicar “Ir a sección”), no para eventos de scroll arbitrarios.
  • Modo reduce: desactivar scroll suave.

Canvas/WebGL: “movimiento reducido” puede significar “pausar”

Si ejecutas un bucle de render siempre activo, trata el modo reduce como una pista para detener o limitar. Sustituye el movimiento de fondo por un frame estático, o renderiza bajo demanda. Bonus: vida de batería mejora, el ventilador deja de intentar despegar y nadie abre un ticket por mareos.

Broma #2: Si tu spinner de carga necesita soporte de movimiento reducido, felicidades—has inventado un nuevo tipo de prueba de estrés para humanos.

Tareas prácticas con comandos (y decisiones)

Estas son tareas prácticas que puedes ejecutar hoy. Cada una incluye: un comando, un ejemplo de salida, qué significa y qué decisión tomas a partir de ello.

Task 1: Encontrar cobertura de reduced-motion en CSS en tu repo

cr0x@server:~$ rg -n "prefers-reduced-motion" .
./src/styles/motion.css:12:@media (prefers-reduced-motion: reduce) {
./src/components/Carousel/carousel.css:88:@media (prefers-reduced-motion: reduce) {

Significado: Tienes dos bloques explícitos de reduced-motion. Eso puede ser genial—o puede significar que la mayoría de componentes lo ignoran.

Decisión: Si menos de tus fuentes principales de motion aparecen aquí, planifica una auditoría de motion (ver checklist). El movimiento reducido debe ser sistemático, no incidental.

Task 2: Inventario de animaciones y transiciones CSS

cr0x@server:~$ rg -n "(animation:|animation-|transition:|transition-)" src
src/styles/base.css:41:transition: all 300ms ease;
src/styles/toast.css:9:animation: slideIn 450ms cubic-bezier(.2,.8,.2,1);
src/components/Hero/hero.css:22:animation: float 4s ease-in-out infinite;

Significado: Tienes al menos una animación infinita (float) y al menos una peligrosa transition: all.

Decisión: Las animaciones infinitas decorativas deben desactivarse en modo reduce. Reemplaza transition: all por propiedades explícitas (opacity/transform), luego controla duraciones vía tokens de motion.

Task 3: Detectar minas de scroll-behavior: smooth

cr0x@server:~$ rg -n "scroll-behavior:\s*smooth" src
src/styles/base.css:7:html { scroll-behavior: smooth; }

Significado: El scroll suave es global. Eso es una violación clásica de movimiento reducido.

Decisión: Cambia a condicional: por defecto apagado o encuéntralo en scope. En modo reduce, fuerza scroll-behavior: auto.

Task 4: Localizar bucles requestAnimationFrame que puedan correr para siempre

cr0x@server:~$ rg -n "requestAnimationFrame\(" src
src/visuals/background.ts:55:rafId = requestAnimationFrame(tick);
src/components/Chart/liveChart.ts:112:window.requestAnimationFrame(draw);

Significado: Hay animaciones/loops impulsados por JS.

Decisión: Asegura que estos bucles respeten el movimiento reducido: pausar, renderizar una vez, o cambiar a renderizado por eventos. También asegúrate de limpiar al desmontar para evitar fugas.

Task 5: Detectar defaults de Framer Motion / librerías de animación

cr0x@server:~$ rg -n "(framer-motion|useReducedMotion|motion\.)" src
src/app/App.tsx:14:import { MotionConfig } from "framer-motion";
src/components/Modal/Modal.tsx:6:import { motion, useReducedMotion } from "framer-motion";

Significado: Usas una librería que tiene opiniones sobre motion. Bien—si está configurada de forma central.

Decisión: Verifica que exista una configuración global (p. ej., MotionConfig) y que las anulaciones a nivel de componente no re-activan motion en modo reduce.

Task 6: Detectar uso de Lottie (a menudo ignora reduced motion por defecto)

cr0x@server:~$ rg -n "(lottie|bodymovin)" src
src/components/EmptyState/EmptyState.tsx:3:import Lottie from "lottie-react";
src/components/PromoBanner/PromoBanner.tsx:8:import lottieData from "./promo.json";

Significado: Hay animaciones Lottie en la UI del producto, probablemente en loop.

Decisión: En modo reduce: no autoplay; mostrar un frame estático o una imagen alternativa. Trata esto como obligatorio, no “agradable de tener”.

Task 7: Verificar que tus tokens de diseño incluyan tokens de motion

cr0x@server:~$ rg -n "(--duration|--easing|motion)" src/styles
src/styles/tokens.css:12:--duration-fast: 120ms;
src/styles/tokens.css:13:--duration-medium: 220ms;
src/styles/tokens.css:14:--duration-slow: 360ms;

Significado: Tienes variables CSS relacionadas con motion. Buena base.

Decisión: Añade overrides de reduced-motion para estas variables en lugar de intentar poner todo en 0 con !important. Ejemplo: acortar y eliminar easings con rebote.

Task 8: Confirmar que tests Playwright puedan emular reduced motion

cr0x@server:~$ rg -n "reducedMotion" tests
tests/e2e/login.spec.ts:9:  await page.emulateMedia({ reducedMotion: "reduce" });

Significado: Al menos un test usa emulación de movimiento reducido.

Decisión: Amplía la cobertura a páginas con mucho motion. Añade un test que aserte que no hay animaciones infinitas ni comportamiento de scroll suave en modo reduce (en la medida que puedas verificar vía DOM/CSS).

Task 9: Revisión rápida de tu bundle por polyfills de scroll suave

cr0x@server:~$ rg -n "(smoothscroll|scrollTo\(\{|behavior:\s*\"smooth\")" src
src/lib/navigation.ts:28:window.scrollTo({ top: 0, behavior: "smooth" });

Significado: JS está forzando scroll suave, sin importar la configuración del usuario.

Decisión: Protege esta llamada: si el movimiento reducido está activado, usa behavior: "auto" u omite behavior.

Task 10: Identificar dependencias de eventos “transitionend/animationend”

cr0x@server:~$ rg -n "(transitionend|animationend)" src
src/components/Drawer/Drawer.tsx:88:el.addEventListener("transitionend", onDone);

Significado: La lógica del componente depende de que las transiciones terminen.

Decisión: En modo reduzido, puedes acortar tanto las duraciones que los eventos se comporten distinto, o desactivar transiciones por completo. Añade una vía explícita: si el movimiento reducido está activado, llama a onDone() inmediatamente (o después de una microtarea), no esperes eventos que pueden no dispararse.

Task 11: Confirmar scripts de terceros que puedan animar overlays

cr0x@server:~$ ls -1 public/vendor
chat-widget.js
marketing-overlay.js

Significado: Existen scripts de vendor que pueden inyectar animaciones fuera del control de tu CSS.

Decisión: Audita a los vendors. Si no soportan movimiento reducido, envuélvelos: desactiva autoplay, suprime overlays o carga configuraciones alternas cuando el modo reduce esté activo.

Task 12: Verificación local en Chromium vía DevTools (la emulación no es la realidad, pero ayuda)

cr0x@server:~$ chromium --user-data-dir=/tmp/chrome-prm --enable-features=WebContentsForceDark
[12874:12874:1229/101512.116955:INFO:chrome_main_delegate.cc(594)] Started

Significado: Lanzaste un perfil limpio de Chromium para evitar ruido de extensiones. (La flag mostrada no es sobre motion; el punto es: aislar variables.)

Decisión: En el panel Rendering de DevTools, emula “prefers-reduced-motion” e inspecciona visualmente flujos clave. Luego repite con el ajuste de SO real activado para detectar discrepancias.

Task 13: Prueba rápida de que no estás forzando “no-preference” globalmente en CSS

cr0x@server:~$ rg -n "prefers-reduced-motion:\s*no-preference" src
src/styles/motion.css:33:@media (prefers-reduced-motion: no-preference) {

Significado: Tienes estilos explícitos para el modo por defecto. Está bien.

Decisión: Asegura que tu bloque “no-preference” no anule accidentalmente el modo reduce por especificidad. Prefiere un override para “reduce” que gane (y pruébalo).

Task 14: Buscar animaciones CSS que puedan ejecutarse incluso cuando están ocultas

cr0x@server:~$ rg -n "infinite" src/styles src/components
src/components/Hero/hero.css:22:animation: float 4s ease-in-out infinite;
src/components/Background/bg.css:11:animation: shimmer 1.6s linear infinite;

Significado: Existen bucles infinitos. Los efectos shimmer en skeleton screens son un ofensor común.

Decisión: En modo reduce, convierte el shimmer en un placeholder estático. Para float en hero: congelar. Tu CPU (y tus usuarios) te lo agradecerán.

Tres micro-historias corporativas desde el terreno

Micro-historia 1: El incidente causado por una suposición errónea

Un equipo SaaS B2B de tamaño medio lanzó un rediseño con una nueva barra lateral elegante. El drawer se deslizaba, el fondo se desenfocaba y el overlay aparecía con fade. También tenían un toggle de movimiento reducido—técnicamente. Se implementó con un snippet CSS global que ponía las duraciones de animación casi a cero en modo reduce.

En papel, parecía compatible. En la práctica, el componente drawer escuchaba transitionend para mover el foco dentro del drawer y para ajustar aria-hidden correctamente en el contenido de fondo. Con la duración prácticamente a cero, el evento no se disparaba consistentemente entre navegadores. A veces se disparaba antes de que el listener se adjuntara. A veces no se disparaba.

Los usuarios con movimiento reducido activado empezaron a reportar “navegación por teclado rota” y “la página está atascada”. Soporte lo escaló como una caída de accesibilidad. Ingeniería inicialmente lo trató como un caso marginal porque “solo ocurre con movimiento reducido activado”. Esa frase envejeció mal en una hora.

La solución fue aburrida y correcta: la máquina de estados del drawer dejó de depender de eventos de transición como fuente de verdad. Las transiciones pasaron a ser cosméticas. Si el modo reduce está activado, ejecuta una ruta determinista: establecer estado DOM, poner foco, saltar animaciones y llamar a la limpieza de forma síncrona.

La suposición errónea no era sobre CSS. Era la creencia de que el movimiento reducido es “solo duraciones más cortas”. No lo es. Es un modo de ejecución distinto.

Micro-historia 2: La optimización que salió mal

Un gran equipo de producto reemplazó varias transiciones CSS por un orquestador de animación en JavaScript para “agrupar actualizaciones” y “evitar thrash de layout”. También consolidaron muchas micro-interacciones en una línea de tiempo compartida.

El rendimiento mejoró en benchmarks. Los informes del mundo real empeoraron. El orquestador usaba bucles requestAnimationFrame que corrían continuamente para “responsividad”, incluso cuando las animaciones estaban inactivas. También activaba por defecto el scroll suave cuando el usuario navegaba dentro de una página. El soporte para movimiento reducido se implementó solo en CSS, así que ninguna lógica de animación JS lo respetaba.

Los usuarios con movimiento reducido seguían viendo parallax y easing en el scroll. Algunos también notaron mayor consumo de batería porque el bucle rAF continuo mantenía la página “caliente”. El equipo culpó al SO. Luego al navegador. Luego a “quizás los usuarios lo configuraron mal”. Clásico.

Lo arreglaron haciendo algo que se sintió como ir hacia atrás: movieron la mayoría de las interacciones de vuelta a CSS (donde la plataforma puede optimizar), y construyeron un único módulo JS de “preferencia de motion” que regía todos los puntos de entrada de animación. También cambiaron el bucle rAF para que fuera dirigido por eventos: solo correr mientras una animación esté activa.

El problema no fue que las animaciones JS sean siempre malas. Fue que la optimización ignoró el requisito operativo: el movimiento reducido debe aplicarse consistentemente a todas las fuentes de movimiento.

Micro-historia 3: La práctica aburrida pero correcta que salvó el día

Una app web del ámbito financiero tenía un proceso de releases estricto. Nada glamuroso. El equipo mantenía una pequeña “lista de contrato de accesibilidad” para cada cambio UI. Un ítem: “Probar con movimiento reducido activado en al menos un SO, y en E2E automatizados para flujos con mucho motion”.

Un diseñador propuso un nuevo flujo de onboarding con ilustraciones animadas y un “tour guiado” que hacía scroll. Ingeniería lo implementó usando una librería de animación popular y un par de archivos Lottie. En pre-release, QA ejecutó la checklist de movimiento reducido y encontró que el tour seguía auto-desplazándose entre pasos, incluso con el modo reducido activado.

La solución fue simple: en modo reduce, el tour dejó de avanzar automáticamente y sustituyó las transiciones de scroll por saltos instantáneos más una gestión clara del foco. Las animaciones Lottie se reemplazaron por frames estáticos. Sin drama, sin incidente, sin disculpas públicas.

Nada de esto fue ingenioso. La práctica funcionó porque era repetible, aplicada y estaba atada a las releases como un cinturón de seguridad. No la notas hasta que realmente la necesitas.

Errores comunes (síntomas → causa raíz → solución)

1) Síntoma: “Movimiento reducido activado, pero el parallax sigue”

Causa raíz: El parallax está implementado en handlers de scroll JS (o en una librería) que nunca comprueba la preferencia.

Solución: Protege la inicialización del parallax con la preferencia. En modo reduce, renderiza un fondo estático y elimina listeners de scroll.

2) Síntoma: “Movimiento reducido rompe modales/drawers (foco atascado, overlay raro)”

Causa raíz: Los cambios de estado dependen de eventos transitionend/animationend.

Solución: Haz el estado del componente determinista; trata las animaciones como opcionales. En modo reduce, llama a los manejadores de finalización directamente y gestiona el foco de forma síncrona.

3) Síntoma: “Todo se engancha instantáneamente y se siente peor”

Causa raíz: Reset global de “duración a 0ms”. Los saltos repentinos pueden desorientar.

Solución: Acorta en lugar de eliminar para transiciones esenciales; elimina solo movimiento decorativo. Usa tokens de motion y ajusta easings.

4) Síntoma: “Los skeleton loaders se shimmer forever incluso en modo reduce”

Causa raíz: Animación CSS infinita no sobrescrita en modo reduce.

Solución: Reemplaza el shimmer por un placeholder estático en modo reduce. Considera usar un bloque de color sutil sin movimiento.

5) Síntoma: “El scroll suave aún se activa en modo reduce”

Causa raíz: scroll-behavior: smooth establecido globalmente, o scrollTo({behavior:"smooth"}) invocado sin condicionales.

Solución: En modo reduce fuerza scroll-behavior: auto, y protege el comportamiento de scroll JS por preferencia.

6) Síntoma: “Movimiento reducido funciona al cargar la página pero no tras alternar en el SO”

Causa raíz: La preferencia se lee una vez y se cachea; no hay listener de cambios.

Solución: Suscríbete a cambios de media query y actualiza una store centralizada; re-renderiza configuraciones de motion según corresponda.

7) Síntoma: “Banner de marketing de terceros sigue animando”

Causa raíz: Widget del vendor ignora reduced motion e inyecta su propio CSS/JS.

Solución: Carga una configuración vendor para modo reduce, o suprime el widget en ese modo. Haz esto un requisito de aprovisionamiento.

8) Síntoma: “El uso de CPU se mantiene alto incluso cuando no pasa nada”

Causa raíz: Bucle rAF inactivo, bucle de render canvas o fondo animado corriendo continuamente.

Solución: Deja de renderizar cuando no sea necesario; en modo reduce por defecto en pausa/estático. Asegura limpieza al desmontar.

9) Síntoma: “Movimiento reducido rompe analytics o tests A/B”

Causa raíz: Código de experimentos depende de tiempos de animación, o mide eventos ligados a transiciones.

Solución: Usa eventos de estado explícitos en lugar de eventos de animación. Instrumenta eventos de negocio independientes del motion.

Listas de comprobación / plan paso a paso

Paso 1: Haz un inventario de motion (una tarde, alto ROI)

  1. Busca animaciones/transiciones CSS y lista los componentes que las usan.
  2. Busca uso de rAF y handlers de scroll.
  3. Lista widgets de terceros que dibujan en pantalla (anuncios, chat, overlays de marketing).
  4. Clasifica cada ítem de motion como esencial/útil/decorativo.

Paso 2: Establece tokens de motion (para cambiar comportamiento sin buscar en todo el repo)

  1. Crea tokens de duración (rápido/medio/lento) y tokens de easing (estándar/enfatizado).
  2. Reemplaza duraciones hardcodeadas y “transition: all” por tokens y propiedades explícitas.
  3. En modo reduce, acorta duraciones y elimina easings con rebote/overshoot.

Paso 3: Implementa movimiento reducido en CSS primero

  1. Desactiva animaciones decorativas infinitas.
  2. Desactiva el scroll suave.
  3. Asegura que interacciones clave sigan transmitiendo estado (anillos de foco, cambios de selección).

Paso 4: Implementa movimiento reducido en JS con un único módulo de preferencia

  1. Envuelve matchMedia en una utilidad que exponga el valor actual y un mecanismo de suscripción.
  2. Protege la inicialización de parallax, animaciones autoplay y bucles de render.
  3. Maneja toggles en tiempo de ejecución actualizando el estado de la app (o recargando configuraciones de motion).

Paso 5: Haz componentes resilientes a “sin animación”

  1. Evita depender de transitionend/animationend para corrección.
  2. Asegura gestión determinista de foco y estados ARIA.
  3. Verifica que eliminar animaciones no cambie el layout inesperadamente.

Paso 6: Añade tests automatizados (o tu yo futuro revivirá este bug)

  1. Añade cobertura E2E con emulación de movimiento reducido para flujos clave (auth, checkout, onboarding).
  2. Añade tests de regresión que aseguren que las fuentes principales de motion están deshabilitadas (por ejemplo, no hay animaciones CSS infinitas en páginas críticas).
  3. Añade una regla de lint o chequeo en CI para marcar scroll-behavior: smooth y “transition: all” a menos que estén aprobados explícitamente.

Paso 7: Operacionalízalo

  1. Añade verificación de movimiento reducido a la aprobación de releases.
  2. Documenta tu “política de motion” en el repo (corta, aplicada, actualizada).
  3. Asegura que los proveedores terceros sean revisados por soporte de movimiento reducido.

Preguntas frecuentes

P1: ¿El movimiento reducido es solo para personas con discapacidades?

No. La sensibilidad al movimiento puede ser situacional o temporal: migrañas, medicación, recuperación de una conmoción, vértigo, fatiga, incluso viajar en un tren con baches. Construye para personas, no para etiquetas.

P2: ¿Deberíamos desactivar todas las transiciones cuando el movimiento reducido esté activado?

Desactiva movimiento decorativo y bucles infinitos. Para transiciones esenciales, prefiere cambios cortos y sutiles (a menudo opacidad) en lugar de movimientos dramáticos. La meta es “sin movimiento”, no “sin retroalimentación”.

P3: ¿Es “duración: 0ms” una estrategia válida?

Es un instrumento contundente. Puede romper componentes que dependen de eventos de animación o temporización, y producir saltos bruscos. Usa tokens y rutas lógicas explícitas para corrección.

P4: ¿Cómo manejamos el scroll suave?

Desactívalo en modo reduce. También evita habilitarlo globalmente. Si debes usarlo para un enlace de salto, condiciona el comportamiento en JS y mantenlo iniciado por el usuario.

P5: ¿Y las micro-interacciones como efectos hover?

Los efectos hover pueden mantenerse si son sutiles y no móviles (color/opacidad). Evita traducciones o rotaciones basadas en hover en modo reduce, especialmente en UIs densas donde el hover ocurre con frecuencia.

P6: ¿Necesitamos responder a cambios de preferencia mientras la app está abierta?

Sí. Los usuarios pueden cambiar ajustes del SO sin reiniciar la pestaña. Escucha cambios de media query y actualiza tu configuración de motion.

P7: ¿Cómo lidiar con librerías de animación?

Elige librerías que expongan un modo reduced-motion o acepten parámetros. Configura de forma central. Luego aplica una regla: ningún componente debe anular el comportamiento de reduced-motion para reactivar animaciones.

P8: ¿Cuál es la forma más rápida de detectar regresiones de movimiento reducido?

Añade un test E2E leve que cargue una página con mucho motion emulada en modo reduce y aserte que elementos clave no animan (o que faltan clases de animación). Acompáñalo con una comprobación manual a nivel de SO en QA de release.

P9: Si el movimiento reducido está activado, ¿podemos seguir usando spinners?

Sí, pero prefiere indicadores no rotacionales: barras de progreso, puntos que aparecen sin movimiento, o texto estático “Cargando…”. Si mantienes un spinner, detén su rotación en modo reduce o cámbialo por un fade sutil.

Conclusión: pasos siguientes que puedes hacer esta semana

El soporte de movimiento reducido no es un ítem de “acabado”. Es un modo de ejecución que afecta corrección, rendimiento y la confianza del usuario. Implémentalo como cualquier requisito operativo: de forma central, testeable y con un plan para el caos de terceros.

Pasos prácticos:

  1. Ejecuta las búsquedas en el repo arriba y construye un inventario de motion.
  2. Elimina el scroll suave global y las animaciones decorativas infinitas en modo reduce.
  3. Implementa un único módulo JS de preferencia y conéctalo a tus puntos de entrada de animación.
  4. Arregla componentes que dependen de eventos de animación para su corrección.
  5. Añade al menos un test E2E de movimiento reducido para tu flujo de usuario principal.
  6. Añade una línea en la checklist de release para que esto no retroceda silenciosamente el próximo trimestre.

Si haces solo una cosa: deja de tratar el movimiento como decoración. En producción, el movimiento es comportamiento. El comportamiento necesita controles.

← Anterior
MySQL vs MariaDB: la latencia en el checkout de WooCommerce — una configuración la soluciona, la otra solo la enmascara
Siguiente →
La VM de Proxmox no arranca tras cambiar el tipo de CPU: pasos de recuperación que funcionan

Deja un comentario