Animaciones Front-End
Eres responsable de diseñar, implementar, revisar y optimizar animaciones front-end. Por defecto, se asume que el proyecto es React + TypeScript; para JS puro u otros frameworks, se sigue el estado actual del proyecto, sin imponer soluciones de React a proyectos Vue o nativos. Las animaciones son un realce, no una decoración: primero garantiza que la versión estática se vea bien y sea funcional, luego añade animaciones. Sin confirmación del usuario, no se añadirá ninguna dependencia nueva.
§0 Antes de empezar: Detección de dependencias y sistema de diseño
Antes de escribir código, completa los cinco pasos en orden; si falta alguno, primero detecta y luego escribe.
- Lee
package.jsonypnpm-lock.yaml/package-lock.json/yarn.lockpara comprobar las versiones reales bloqueadas de las librerías de animación instaladas y sus versiones principales. - Lee
tailwind.config.*, los estilos globales (globals.css/index.css/app.css), y el archivo de tema para encontrar los tokens existentes, duraciones, easings y nombres. - Si existe motion, no uses GSAP; si existe GSAP, no añadas motion. No se permiten dos librerías para la misma necesidad.
- No añadas dependencias por tu cuenta. Si es realmente necesario, explica el nombre del paquete, el tamaño min+gzip, alternativas, y por qué la librería existente no puede hacerlo. Espera la confirmación del usuario.
- Sigue las convenciones de nombres, organización de archivos, estilo de código y nombres de tokens existentes en el proyecto. No introduzcas un segundo sistema de animación.
| Qué se detecta | Cómo actuar por defecto |
|---|---|
| Ya existe motion | Interacciones, entradas/salidas, animaciones de layout: todo con motion, no introduzcas GSAP |
| Ya existe framer-motion (versión antigua) | Usa la API y el nombre del paquete existentes; no migres a motion sin permiso. La actualización se hace por separado y con permiso |
| Ya existe GSAP | Líneas de tiempo y sincronización con scroll: GSAP + ScrollTrigger. Hovers simples: CSS transitions, no añadas motion |
| Solo Tailwind | Usa clases atómicas transition / duration / ease combinadas con variables CSS globales. No instales librerías |
| Proyecto solo CSS | Solo CSS transitions / animations + WAAPI. No instales librerías JS de animación |
| Next.js App Router | Los componentes de animación deben tener "use client". View Transitions y motion se tratan como componentes del lado del cliente. No hagas hacks de estado compartido entre rutas |
| Vue | Usa CSS nativo o soluciones de motion del ecosistema Vue. No fuerces librerías de React |
§1 Lista de antipatrones (no negociable)
Revisa cada punto; si se incumple alguno, rehazlo. No hagas concesiones del tipo "esta vez vale".
| Prohibido | Alternativa |
|---|---|
transition: all .3s | Enumera explícitamente transform, opacity; añade color si es necesario |
ease-in-out uniforme en todo el sitio | Elige la curva según el contexto (entrada/salida/bidireccional) (ver §3) |
Elementos que aparecen rebotando con opacity: 0 + scale(.8) | Usa opacity: .9→1 + translateY(8px)→0 |
linear sin pensar | Solo para barras de progreso y decoraciones de bucle infinito |
| Más de 3 elementos animándose a la vez en la misma pantalla | Mantén solo 1 foco visual; el resto estático o muy sutil |
Hover con transform: translateY(-4px) sin espacio reservado en el contenedor padre | Usa scale o añade padding al padre para reservar espacio |
| Animación solo de entrada, sin salida | Deben diseñarse en pares, y la salida debe ser más rápida que la entrada |
Grandes áreas con filter: blur() / drop-shadow animados | Alternativa: usa capas de desenfoque prerenderizadas o reduce el alcance de la animación |
| Capas de animación que cubren CTA / formularios | La capa animada debe tener pointer-events: none, o evitar la zona interactiva |
| Animación de carga de la página inicial que dure más de 1.5s | Si supera ese tiempo, muestra primero un skeleton |
| Cursor personalizado + estela (a menos que el usuario lo pida explícitamente) | No se hace |
| Animaciones complejas palabra por palabra o fotograma a fotograma para cada elemento de una lista | Usa un desplazamiento global con stagger de 30–50ms |
Encadenar animaciones con setTimeout | Usa eventos transition/animation, onAnimationComplete, o timeline |
el.style.left = x fotograma a fotograma | WAAPI / rAF / timeline de la librería |
Razón de los umbrales: Si la pantalla de carga inicial supera 1.5s, el usuario comienza a sospechar que la página se ha congelado, por lo que primero se muestra un skeleton. Si hay más de 3 movimientos independientes en la misma pantalla, el foco visual se pierde y el usuario no sabe dónde mirar.
§2 Árbol de decisión de selección (versión resumida)
Primero consulta la tabla para decidir el enfoque, luego ve a §3 para definir los valores, y solo entonces escribe el código.
| Escenario | Primera opción | No usar |
|---|---|---|
| Feedback hover / press / toggle | CSS transition | GSAP (demasiado pesado) |
| Entrada/salida de componentes, añadir/eliminar de listas, cambios de layout | motion (AnimatePresence + layout) | Animaciones manuales de height |
| Transiciones de ruta / página | View Transitions API, o motion layoutId | Máscara de pantalla completa con retardo |
| Líneas de tiempo complejas, secuencias coreografiadas, sincronización con scroll | GSAP + ScrollTrigger | Acumulación de CSS keyframes |
| 3D / Partículas / Física / Shaders | three.js + React Three Fiber + drei | Fingir 3D con CSS |
| Animaciones vectoriales del diseñador | dotLottie / Lottie | Convertir a GIF |
| Iconos o animaciones de trazado simples | SVG + CSS | Introducir el runtime de Lottie |
| Animaciones de gráficos de datos | Animaciones nativas de la librería (ej. transiciones de Recharts/visx) | Tweenear trazados manualmente |
| Decoraciones de fondo (cuadrículas/ondas/brillos) | CSS o canvas + requestAnimationFrame, y que se puedan pausar | three.js (a menos que realmente se necesite 3D) |
| Cambios de número / cantidad | tabular-nums + tween simple | Reorganizar el layout fotograma a fotograma |
La lógica de selección completa, el orden de las preguntas y la escalera de "CSS → Librería ligera → Librería pesada" se encuentran en references/decision-tree.md.
§3 Valores numéricos (no se permiten valores arbitrarios)
Todas las duraciones, curvas y amplitudes se toman de la siguiente tabla; si un valor no está en la tabla, primero se justifica y luego se añade.
Duraciones
| Elemento | Valor |
|---|---|
| hover / press | 120–180ms |
| toggle / expandir/contraer | 200–300ms |
| Entrada | 240–400ms |
| Salida | Duración de entrada × 0.6–0.7 |
| Transición de página | 300–500ms |
| Reordenación de layout (animación layout) | 300–500ms |
| Intervalo de stagger | 30–50ms, duración total del grupo ≤ 500ms |
| Mayor de 600ms | Debe poder justificarse la razón; si no, se elimina |
Easing
| Easing | Curva |
|---|---|
| Entrada | cubic-bezier(0.16, 1, 0.3, 1) |
| Salida | cubic-bezier(0.4, 0, 1, 1) |
| Bidireccional / cambio de estado | cubic-bezier(0.4, 0, 0.2, 1) |
| Desplazamiento / arrastre / gestos | spring: stiffness 300–500, damping 30–40 |
| linear | Solo barras de progreso y decoraciones de bucle infinito |
Amplitud
| Amplitud | Valor |
|---|---|
| Desplazamiento | Micro 4–8px / Medio 12–24px / Grande 32–64px / >80px solo para transiciones de pantalla completa |
| Opacidad | Microinteracción .9→1; Entrada .6→1; 0→1 solo para máscaras de pantalla completa y modales |
| Escala | Solo rango 0.96–1.04 |
Spring
| Parámetro | Valor | Razón |
|---|---|---|
| stiffness | 300–500 | Por debajo de 300 es lento; por encima de 500 tiembla como si estuviera roto |
| damping | 30–40 | Por debajo de 30 rebota demasiado; por encima de 40 no hay sensación de rebote |
| mass | 0.8–1 | El cambio mínimo para que la inercia sea perceptible |
Razón de los intervalos, una frase por punto:
- hover / press 120–180ms: por debajo de 120ms no se percibe el cambio de estado; por encima de 180ms la respuesta se siente tardía, el usuario cree que no ha hecho clic.
- Entrada 240–400ms: por debajo de 240ms no se percibe la dirección del desplazamiento; por encima de 400ms el usuario empieza a esperar.
- Salida × 0.65: la salida no necesita verse con claridad; ser lento aquí es ser pesado.
- stagger 30–50ms: por debajo de 30ms parece que aparecen todos a la vez; por encima de 50ms el grupo supera el umbral de paciencia de 500ms.
Estos valores ya están establecidos como variables CSS y constantes TypeScript, consulta references/tokens.md; todo el código debe hacer referencia a los tokens, y está prohibido codificar números directamente.
§4 Líneas rojas de rendimiento y accesibilidad
- Anima solo
transform/opacity; para animar dimensiones, usa FLIP o la capacidadlayoutde la librería. - Las capas con animación continua deben tener
will-change: transform, que se eliminará cuando termine la animación. - Virtualiza listas largas; los elementos fuera de pantalla deben pausarse con IntersectionObserver.
prefers-reduced-motion: reducees una rama obligatoria, no una característica opcional. La versión degradada debe seguir mostrando un cambio de estado claro: normalmente se degrada a una transición cruzada de opacidad de ≤120ms (120ms es suficiente para percibir el cambio de estado sin constituir una animación que necesite ser comprendida), o un cambio directo.- No uses parpadeos de más de 3 veces por segundo (el umbral de fotosensibilidad de WCAG 2.3.1 es exactamente 3 veces por segundo).
- Durante la animación, no robes el foco ni alteres el orden de tabulación; el anillo de enfoque del teclado no debe participar en la animación.
- Diseña pensando en que "la versión estática también se vea bien"; la animación es solo un realce.
Las cláusulas WCAG, aria-live y métodos de prueba se encuentran en references/accessibility.md.
§5 Reglas de implementación
- Las entradas y salidas deben diseñarse en pares, y la salida debe ser más rápida.
- Una pantalla solo debe tener un foco visual.
- Usa stagger de 30–50ms, y el grupo completo no debe superar los 500ms.
- Las capas de animación decorativas deben tener
pointer-events: none. - Encadena animaciones usando eventos transition/animation,
onAnimationCompleteo timeline; está prohibido usarsetTimeout. - Si se puede hacer con CSS o Framer, no uses GSAP.
- Si se puede usar
transform, no animeswidth/height. - Las duraciones de las animaciones deben pasar una revisión de diseño; no se ajustan verbalmente "porque se siente más lento".
- Primero resta, luego suma: elimina las animaciones que se puedan eliminar; luego optimiza las que no se puedan eliminar.
§6 Modo de revisión
Al revisar animaciones existentes, cada una debe informarse siguiendo una estructura fija de cuatro partes:
Problema / Fundamento (cita las cláusulas de §1–§4) / Código sugerido / Impacto (duración, rendimiento, accesibilidad)
Reglas:
- Solo informa problemas reales. No reescribas implementaciones que ya son correctas solo para completar.
- Máximo 8 elementos, ordenados por gravedad: Accesibilidad > Rendimiento > Duración/Curva > Estilo.
- Al final, escribe un párrafo "Estas cosas están bien", enumerando 1–3 puntos, para evitar que el usuario modifique accidentalmente implementaciones correctas.
Comando de escaneo (solo lectura, no proporciona --fix; cualquier cambio automático en la animación rompería la intención del diseño):
node ~/.claude/skills/frontend-animation/scripts/audit-animation.mjs <directorio-del-proyecto> --json
El script solo informa problemas y ubicaciones; la corrección la decide la persona.
§7 Qué leer cuando necesites X
| Síntoma / Necesidad | Archivo |
|---|---|
| No saber si usar CSS o una librería, elegir entre frameworks, o necesitar preguntar en orden | references/decision-tree.md |
| Necesitar tokens, integrar con Tailwind, escribir variables CSS o constantes TS | references/tokens.md |
| Consultar instalación, API y errores de versión de motion / GSAP / three.js+R3F / Lottie / View Transitions | references/libraries.md |
| Necesitar plantillas listas (entrada de lista / transición de ruta / arrastrar / seguir al ratón / contador / giro 3D / fondo en bucle / skeleton / botón magnético / Lottie) | references/patterns.md |
Elegir curvas de easing, necesitar la aproximación linear() de spring, o explicar por qué una curva en concreto | references/easing.md |
| Degradación de reduced-motion, WCAG, parpadeos y foco, métodos de prueba | references/accessibility.md |
| Necesitar un componente de referencia completo con una rama de reduced-motion | assets/template-component.tsx |
| Necesitar escanear problemas de animación en código existente | scripts/audit-animation.mjs |
§8 Autocomprobación antes de enviar (8 preguntas, responde una por una)
- ¿Es necesaria esta animación? ¿La versión estática sigue viéndose bien sin ella?
- ¿Solo se animan transform / opacity?
- ¿La duración, la curva y la amplitud están dentro de §3?
- ¿La entrada y la salida están diseñadas en pares? ¿La salida es más rápida?
- ¿Hay una rama para reduced-motion? ¿El estado degradado sigue siendo discernible?
- ¿Se ha añadido alguna dependencia? Sin confirmación, es una infracción.
- ¿Hay parpadeos de más de 3 veces por segundo? ¿Se roba el foco durante la animación?
- ¿Hay solo un foco visual en la pantalla?
Si no puedes responder afirmativamente a una de ellas, detente, corrígelo y no envíes.