wandres.dev
ACCESIBILIDAD · prefers-reduced-motion en serio

Reducir no es eliminar: el patrón de dos versiones

Qué significa reducir el movimiento según lo hacen los propios sistemas operativos, la tabla de sustituciones que funciona, cómo diseñar la segunda versión de cada animación y qué se rompe si la eliminas del todo.

⏱ 19 min

La palabra en el nombre del ajuste es reducir, no desactivar, y no es una elección casual: cuando activas la preferencia en macOS, el sistema no deja de animar, cambia el zoom de las ventanas por un fundido cruzado. Ese es el modelo correcto y el que hay que copiar. El movimiento no es el enemigo; el movimiento que simula desplazamiento propio lo es, y casi siempre hay una forma de comunicar lo mismo sin él.

🎯 Al terminar esta lección sabrás
  • Justificar por qué el objetivo es sustituir el movimiento y no suprimirlo.
  • Aplicar la tabla de sustituciones a los patrones de interfaz más frecuentes.
  • Implementar dos versiones de una animación desde un único juego de tokens.
  • Identificar los casos en los que suprimir el movimiento perjudica la comprensión.

Reducir es sustituir

Mira lo que hacen los sistemas operativos cuando activas el ajuste, porque llevan una década afinándolo con datos de usuarios reales.

macOS sustituye el zoom de apertura de una ventana por un fundido cruzado. iOS sustituye el deslizamiento entre pantallas por un fundido, y tiene incluso una opción específica llamada Preferir transiciones de fundido cruzado. Windows reduce los efectos y mantiene los cambios de estado visibles. Ninguno de los tres corta el movimiento en seco y deja que las cosas aparezcan de golpe.

La razón es que la transición cumple una función que no es decorativa: señala que algo ha cambiado y de dónde a dónde. Una ventana que aparece instantáneamente en pantalla completa obliga al ojo a reconstruir qué ha pasado; un fundido de doscientos milisegundos lo dice sin ambigüedad. Quitar la transición no devuelve al usuario a un estado neutro: lo lleva a un estado distinto, con su propio coste cognitivo, que además recae sobre la misma persona a la que estabas intentando ayudar.

De ahí sale el criterio de esta lección: la versión reducida es otra animación, no la ausencia de una. Y la diferencia entre ambas es que la reducida no contiene señales de desplazamiento propio: no escala, no se desplaza mucho, no tiene capas a distintas velocidades, no gira, y no ocupa media pantalla.

La tabla de sustituciones

Versión completa Versión reducida Por qué
Desliza desde un borde Fundido de opacidad El desplazamiento largo es la señal de vección
Escala desde 0,9 a 1 Fundido, o escala de 0,98 a 1 El cambio de tamaño es movimiento en z
Paralaje de capas Todas las capas fijas La disparidad de profundidad es el multiplicador
Transición de página deslizante Fundido cruzado Toda la pantalla desplazándose
Revelado al hacer scroll Contenido visible desde el principio El scroll ya es movimiento suficiente
Carrusel automático Sin avance automático, controles visibles Movimiento grande no iniciado
Escalonado de una lista Todos a la vez con un fundido El escalonado alarga la exposición
Curva con rebote o elástica Curva monótona de salida Los cambios de dirección son aceleraciones
Brillo que recorre el esqueleto Pulso de opacidad, o esqueleto quieto Un degradado en movimiento sobre gran área
Giro continuo del indicador Pulso de opacidad o barra determinada Rotación continua, aunque pequeña
Desplazamiento suave del scroll Salto directo Movimiento grande no predicho por el gesto
Vídeo de fondo en reproducción Póster estático con botón de reproducir Movimiento continuo a pantalla completa
Confeti o partículas Un icono estático y un mensaje Muchos elementos, muchas direcciones

Dos observaciones sobre esta tabla que valen más que la tabla.

El fundido de opacidad es la respuesta a casi todo. No porque sea elegante, sino porque es el único cambio visual que no contiene ninguna señal de movimiento: nada se desplaza en el campo visual, no hay flujo óptico, no hay vección posible. Por eso puedes conservarlo con tranquilidad y por eso la versión reducida no tiene por qué ser instantánea.

La duración de la versión reducida no debe ser 1 ms. Ese es el valor del bloque universal de emergencia, y es la respuesta correcta cuando no sabes qué está pasando. Cuando sí lo sabes, un fundido de ciento veinte o ciento cincuenta milisegundos comunica el cambio de estado sin ningún riesgo, y es mejor experiencia para todo el mundo que un salto instantáneo.

Implementar las dos versiones

El sistema de tokens de la lección anterior se extiende con un segundo eje. En lugar de un multiplicador que apaga, tienes dos juegos completos.

:root {
  /* Version completa */
  --mov-corta: 140ms;
  --mov-media: 260ms;
  --mov-larga: 420ms;
  --mov-distancia: 1;      /* multiplicador de desplazamiento */
  --mov-escala: 1;         /* multiplicador de cambio de tamano */
  --mov-curva-entrada: cubic-bezier(0, 0, 0.2, 1);
  --mov-curva-enfasis: cubic-bezier(0.34, 1.56, 0.64, 1);
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --mov-corta: 100ms;
    --mov-media: 140ms;
    --mov-larga: 160ms;
    --mov-distancia: 0;
    --mov-escala: 0;
    --mov-curva-entrada: linear;
    --mov-curva-enfasis: linear;  /* el rebote desaparece */
  }
}

Y los componentes se escriben una sola vez, expresando el desplazamiento y la escala como múltiplos de los tokens:

.hoja {
  opacity: 0;
  translate: 0 calc(24px * var(--mov-distancia));
  scale: calc(1 - 0.04 * var(--mov-escala));
  transition:
    opacity var(--mov-corta) linear,
    translate var(--mov-media) var(--mov-curva-entrada),
    scale var(--mov-media) var(--mov-curva-entrada);
}

.hoja[data-abierta] {
  opacity: 1;
  translate: 0 0;
  scale: 1;
}

Con la preferencia activada, --mov-distancia y --mov-escala valen cero, así que translate y scale parten ya de su valor final: no hay nada que animar en esas dos propiedades y la transición se reduce, sin que hayas escrito una segunda regla, a un fundido de cien milisegundos. Con la preferencia desactivada, la hoja sube veinticuatro píxeles y crece un cuatro por ciento. Una sola declaración por componente, dos comportamientos.

En JavaScript, el mismo principio con una función que lee los tokens en lugar de duplicar números:

const raiz = getComputedStyle(document.documentElement);
const num = (nombre) => parseFloat(raiz.getPropertyValue(nombre));
const ms = (nombre) => {
  const v = raiz.getPropertyValue(nombre).trim();
  return v.endsWith("ms") ? parseFloat(v) : parseFloat(v) * 1000;
};

export function aparecer(elemento) {
  const distancia = 24 * num("--mov-distancia");
  return elemento.animate(
    [
      { opacity: 0, transform: `translateY(${distancia}px)` },
      { opacity: 1, transform: "translateY(0)" },
    ],
    { duration: ms("--mov-media"), easing: "cubic-bezier(0, 0, 0.2, 1)", fill: "both" }
  );
}

Cuando el multiplicador es cero, la distancia es cero y el keyframe inicial y el final coinciden en transform: la animación sigue existiendo, sigue disparando finished, y visualmente es solo un fundido. Nada se rompe aguas abajo.

💡
El truco del multiplicador de distancia

Expresar cada desplazamiento como calc(X * var(--mov-distancia)) en lugar de escribir dos reglas es la técnica que hace sostenible el patrón de dos versiones. Sin ella, cada componente necesita su propio bloque @media, y en la revisión número cuarenta alguien va a olvidarse de escribirlo. Con ella, el componente cumple la preferencia por construcción y no hay nada que recordar.

Lo que se rompe si eliminas del todo

Cuatro casos donde suprimir el movimiento empeora la comprensión, con la solución en cada uno.

El aviso emergente. Un mensaje que aparece y desaparece en una esquina sin ninguna transición se pierde: el ojo no tiene nada que le llame la atención hacia esa zona y no hay señal de que algo ha ocurrido. Un fundido de ciento cincuenta milisegundos lo resuelve. Y en cualquier caso, un aviso importante no debería depender solo de eso; tiene que estar en una región aria-live para que se anuncie.

El acordeón. Sin transición de altura, el contenido de abajo salta a otra posición sin explicación y el usuario pierde el punto de lectura. Aquí lo correcto no es quitar la animación sino quitarle el movimiento vertical al contenido interior —que es lo que se percibe como desplazamiento— y conservar la apertura del contenedor, o simplemente acortarla. La altura del propio contenedor es un cambio pequeño y localizado.

El indicador de proceso. Si el giro desaparece y no lo sustituye nada, la interfaz parece congelada. Un pulso de opacidad de un segundo transmite lo mismo sin ninguna rotación. Y si conoces el progreso, una barra determinada es mejor que las dos.

El orden de una lista que se reordena. Sin transición, el usuario no puede seguir el elemento que estaba mirando: es el problema que las animaciones de reordenación existen para resolver. La versión reducida aquí no es “sin animación”: es un fundido corto de todos los elementos a la vez, que al menos separa el antes del después.

El hilo común: cuando el movimiento estaba haciendo un trabajo, hay que sustituir ese trabajo por otra cosa, no dejar el hueco. Y cuando el movimiento no estaba haciendo ningún trabajo, la pregunta interesante no es cómo reducirlo, sino por qué estaba ahí.

La versión reducida es el mejor auditor de si tu movimiento significa algo

Hay un ejercicio que cuesta media hora y cambia cómo un equipo entiende su propio producto: coge una pantalla, activa la preferencia de movimiento reducido y pregunta, para cada animación que has apagado, qué información acaba de perder el usuario. Las respuestas se reparten en dos montones muy desiguales. En el primero, pequeño, hay animaciones cuya desaparición deja un agujero concreto y nombrable: ya no se ve de dónde salió el panel, ya no se sabe si el sistema está trabajando, ya no se puede seguir el elemento que se movió de sitio. Esas son las que hay que sustituir con cuidado, porque estaban comunicando algo que el resto de la interfaz no comunica. En el segundo montón, mucho más grande de lo que nadie espera, hay animaciones cuya desaparición no se nota en absoluto, y ahí la conclusión honesta no es “hay que buscarle una versión reducida”: es que esa animación no estaba comunicando nada y probablemente sobra también en la versión completa. Lo interesante del ejercicio es que la preferencia de movimiento reducido acaba funcionando como un test de necesidad que nadie había diseñado para eso. Ninguna revisión de diseño te va a decir que aquel escalonado de la rejilla no aporta nada, porque en la revisión todo el mundo lo ve bonito y bonito es un argumento; pero apagarlo y comprobar que nadie echa nada de menos sí lo dice, y lo dice sin discusión. Los equipos que hacen este ejercicio con regularidad terminan con menos movimiento, más intencionado y más rápido, y descubren que la accesibilidad no les ha costado calidad sino que se la ha revelado. Es de los pocos sitios donde el trabajo de accesibilidad se paga solo el mismo día en que se hace.