Animación de UX
Aplica estas reglas al diseñar y escribir la interacción. Primero, inspecciona los tokens de movimiento del proyecto: el archivo AGENTS.md, CLAUDE.md, los archivos de tokens y los documentos de diseño del repositorio anulan los valores predeterminados que se indican a continuación.
Núcleo siempre activo
- Decisión predeterminada: la UI funcional generalmente se sitúa en un rango de 120–180ms. Los tokens del proyecto tienen prioridad.
- Anima para mantener continuidad espacial, cambios de estado, retroalimentación de entrada, o para evitar un salto brusco.
- Prefiere transiciones nombradas de
transformyopacity. Nunca usestransition: all. - Las superficies que aparecen deben desacelerar; las que desaparecen deben acelerar. Las salidas pueden ser más cortas.
- Inicia las superficies escaladas cerca de
scale(0.96), nunca desdescale(0). - Mantén el movimiento reemplazable bajo
prefers-reduced-motion: reduce; preserva la comprensión y el acceso por teclado. - Captura el puntero para los arrastres, protege los toques secundarios y prueba los gestos en un dispositivo real.
- Antes de escribir animación o tocar una superposición común, lee este archivo por completo. Usa
REFERENCE.mdsolo para trazados de recorte, 3D, WAAPI o inmersiones profundas en rendimiento.
1. Decide si merece la pena animarlo
Utiliza la frecuencia de exposición como restricción: las acciones que se repiten constantemente deben ser instantáneas o casi; las superficies ocasionales pueden establecer continuidad espacial; los momentos raros y explicativos pueden tener más personalidad. El movimiento debe aclarar un cambio, reconocer la entrada, preservar la ubicación o evitar una aparición brusca. Si no hace ninguna de estas cosas, elimínalo. No hagas que un atajo de teclado espere a una animación decorativa.
2. Elige una duración
El rango de 120–180ms es una decisión predeterminada: es intencionadamente más ajustado que el short3 (150ms), short4 (200ms) y el rango de transición estándar (250–400ms) publicados por Material 3. Expresa una preferencia contra la UI funcional lenta, no un requisito de accesibilidad o plataforma publicado.
| Superficie | Punto de partida |
|---|---|
| Retroalimentación de pulsación | 100–140ms |
| Tooltip o popover pequeño | 120–160ms |
| Menú o desplegable | 140–180ms |
| Modal | 180ms |
| Panel lateral | 180ms; hasta 250ms cuando recorre la mayor parte del viewport |
Usa el extremo más corto para las salidas. Estos son valores por defecto, no anulaciones para los tokens del producto.
3. Elige una curva de aceleración
Una superficie que aparece debe llegar con énfasis y asentarse (desacelerar); una salida debe despejar rápidamente (acelerar). Material 3 nombra estas direcciones explícitamente:
:root {
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
--ease-enter: cubic-bezier(0.05, 0.7, 0.1, 1); /* emphasized-decelerate */
--ease-exit: cubic-bezier(0.3, 0, 0.8, 0.15); /* emphasized-accelerate */
}
Usa linear solo para progreso similar al tiempo. Usa una curva ease-in-out para un estado que viaja a través de un punto medio deliberado. Evita elegir una curva solo por su nombre: inspecciona la forma trazada y la dirección del viaje.
4. Manténlo barato de renderizar
Prefiere transform y opacity, que pueden permanecer en el compositor. Nombra las propiedades y mantén el layout fuera de la animación. Evita animar top, left, width, height, margin o padding a menos que el cambio de diseño sea el objetivo y el coste sea aceptado. Para altura intrínseca, un wrapper puede hacer la transición de grid-template-rows: 0fr a 1fr, con un wrapper interno con min-height: 0 recortado por overflow: hidden. Esto aún realiza re-layout; perfílalo en lugar de tratarlo como un atajo solo del compositor. Usa will-change tarde, brevemente, y solo después de que el perfilado muestre que ayuda; las capas consumen recursos.
5. Haz que los estados sean correctos
- Proporciona retroalimentación visible a los controles pulsables; usa un estado de color, borde o sombra existente, o un pequeño
scale(0.98)cuando sea adecuado. No sobrescribas una transformación de posicionamiento. - Preserva el foco visible, la activación por teclado y el comportamiento deshabilitado. La animación no debe retrasar la colocación del foco, el manejo de Escape o las actualizaciones de estado semántico.
- Los popovers, menús y tooltips crecen desde un ancla mediante
transform-origin; los modales centrados crecen desde el centro. Mantén el origen estable mientras el estado cambia. - Prefiere transiciones para estados que se puedan volver a activar. Usa keyframes para un evento autocontenido.
- Limita las funcionalidades solo de hover con
@media (hover: hover) and (pointer: fine). - Para un elemento que aparece, usa
@starting-styledonde sea compatible o un estado montado que haga que el estilo inicial sea observable antes de la transición.
6. Movimiento reducido significa un reemplazo
La configuración prefers-reduced-motion cubre la eliminación, reducción o reemplazo del movimiento; el desplazamiento y escalado a gran escala pueden ser desencadenantes vestibulares. Reemplaza el desplazamiento y la escala con cambios de opacidad y color donde estos aún expliquen el estado. Mantén los cambios de foco, carga y visibilidad comprensibles. No hagas que un elemento desaparezca simplemente porque el usuario pidió menos movimiento. Respeta esta preferencia en animaciones impulsadas por JavaScript así como en CSS, incluidos los cambios de preferencia mientras la página está abierta. No dependas de un evento transitionend para completar un cambio de estado esencial: la transición puede estar deshabilitada o cancelada. Limita las anulaciones al componente afectado en lugar de forzar globalmente cada animación a una duración mínima.
7. Gestos
Llama a setPointerCapture() cuando comience un arrastre: los eventos de puntero posteriores continuarán apuntando al elemento capturador hasta que se libere. Descarta según una regla de velocidad-o-distancia ajustada; no hay una constante universal de movimiento rápido, así que calíbrala en el dispositivo real. Mantén el movimiento acotado con amortiguación en lugar de un borde duro. Ignora los puntos de contacto adicionales una vez que un gesto posee el puntero. Permite al usuario revertir un gesto en curso. Maneja pointercancel y lostpointercapture limpiando el estado de arrastre. Elige touch-action para el eje del gesto sin bloquear innecesariamente el desplazamiento de la página; proporciona un botón o equivalente de teclado.
8. Manejo de interrupciones y resortes
El cierre/re-apertura/re-apertura rápida debe asentarse en el último estado solicitado. Reorienta desde el estado visual actual en lugar de reproducir desde el principio. Cancela callbacks, listeners y animaciones obsoletas al reemplazar o desmontar; una salida antigua no debe eliminar una superficie recién reabierta. Mantén el contenido oculto o saliente fuera de la navegación por teclado de acuerdo con el ciclo de vida de foco del componente.
Usa un resorte cuando la manipulación directa o la continuidad se beneficien de ello, respetando la biblioteca del proyecto. Los parámetros del resorte varían entre motores: no copies los valores de rigidez y amortiguación a ciegas. Ajusta el tiempo de asentamiento y el rebasamiento en el componente real; evita el rebote en superficies con mucho texto y deshabilita el desplazamiento bajo movimiento reducido.
9. Verifica antes de darlo por terminado
Reproduce a 4× de velocidad más lenta y verifica que las propiedades terminen juntas y se originen desde el lugar correcto. Alterna rápidamente para exponer tirones. Emula el movimiento reducido en DevTools. Realiza un arrastre más allá de su borde, cancélalo, agrega un segundo toque y pruébalo en un dispositivo físico. Para valores de animación calculados en tiempo de ejecución, consulta REFERENCE.md. Indica qué comprobaciones se realizaron realmente y cuáles requieren un navegador o un dispositivo físico; la revisión de código por sí sola no puede establecer la suavidad percibida.
Revisar la salida
Al auditar código, informa una tabla compacta, la más grave primero:
| Archivo:línea | Ahora | Cambiar a | Por qué |
|---|---|---|---|
Menu.tsx:18 | transition: all 300ms | propiedades nombradas y tokens del proyecto | evita la animación accidental de layout |
Informa solo los hallazgos visibles en el código. Si el diff está limpio, dilo en lugar de inventar hallazgos.
Curado por mad7droid.