cssanimacionesrendimientotransform

Animaciones CSS a 60 fps: transform, opacity y las reglas del rendimiento

Por qué animar width o top destroza el rendimiento y transform/opacity no: el pipeline de render, will-change, composición GPU y prefers-reduced-motion.

27 de agosto de 2026·8 min de lectura

Una animación puede ser fluida en el portátil de desarrollo y entrecortada en el móvil de gama media de tu usuario. La diferencia nunca está en la estética sino en QUÉ propiedad animas. Hay dos que son gratis y una docena que cuestan carísimo — entender por qué requiere conocer el pipeline de renderizado del navegador, y ese conocimiento paga para siempre.

El pipeline: layout → paint → composite

Cada frame (idealmente uno cada 16,6 ms para 60 fps), el navegador ejecuta hasta tres fases según lo que cambió:

  1. Layout: calcular geometría — posición y tamaño de cada elemento.
  2. Paint: dibujar píxeles en capas.
  3. Composite: apilar capas ya rasterizadas, en GPU.

La regla de oro completa: animar propiedades que solo tocan composite es barato; las que fuerzan layout son carísimas.

/* CARO: cada frame recalcula layout Y paint, y arrastra a los vecinos */
.malo { transition: width .3s, margin .3s, top .3s; }

/* BARATO: solo compone; ni layout ni paint */
.bueno { transition: transform .3s, opacity .3s; }

¿Por qué width arrastra a los vecinos? Cambiar su ancho reflowea todo el flujo: hermanos desplazados, texto re-wrapeado, contenedores recalcados... En un DOM grande, un solo cambio de layout puede costar más que el presupuesto completo del frame. transform, en cambio, mueve el elemento como capa independiente en GPU sin tocar el flujo — visualmente equivalente para mover/escalar/rotar.

Las sustituciones canónicas

Quieres No uses Usa
Mover top/left/right/bottom transform: translate()
Escalar width/height/font-size transform: scale()
Aparecer/desaparecer display/visibility solos opacity (+ visibility al final)
Color de fondo animarlo mucho opacidad sobre capa con el color destino
Sombras crecientes box-shadow directo pseudo-elemento pre-renderizado + opacity

El caso de la sombra merece detalle porque es trampa común: animar box-shadow fuerza repaint en cada frame. La técnica profesional renderiza la sombra final en un ::after invisible desde el inicio y anima únicamente su opacity:

.tarjeta { position: relative; }
.tarjeta::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: 0 12px 32px rgba(0,0,0,.25);
  opacity: 0;
  transition: opacity .3s;
}
.tarjeta:hover::after { opacity: 1; }

Un repaint inicial (invisible) y luego pura composición.

will-change: prometer antes de prometer

will-change: transform avisa al navegador de que esa propiedad cambiará, permitiéndole preparar (promover a capa propia) ANTES de que empiece la animación. Evita el "hitch" del primer frame — pero tiene coste: cada capa promovida consume memoria GPU.

Reglas de uso honestas:

  • Aplica solo a elementos que VAN a animarse pronto y repetidamente (un carrusel visible, un drawer).
  • NUNCA lo apliques global a docenas de elementos "por si acaso": memoria desperdiciada y más gestión de capas.
  • Elimínalo cuando la animación termina si es puntual (JS: element.style.willChange = 'auto').

Y ojo con el límite práctico: demasiadas capas grandes agotan texturas GPU y provocan crashes de composición en móviles antiguos. Capas sí, con moderación.

Cómo diagnosticar

DevTools → Rendering → activa Paint flashing (pinta verde todo repintado: si tu animación parpadea verde constantemente, estás repinteando), Layout Shift Regions (azul = reflows). El panel Performance graba una sesión y muestra exactamente qué fase se come tu presupuesto de frame. Si ves "Recalculate Style" y "Layout" dominando cada 16 ms, vuelve a la tabla de sustituciones.

Accesibilidad: prefers-reduced-motion

Para usuarios con vestibular disorders (mareos por movimiento), las animaciones grandes no son molestas: son nauseosas. La media query respeta su configuración del sistema:

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

O mejor aún, sustituye por transiciones de opacidad suaves en vez de aniquilarlas. Es accesibilidad básica como el contraste: coste mínimo, impacto real.

Practica con ejemplos vivos

Todos los patrones de este artículo están implementados en nuestros spinners: el generador de loaders CSS emite seis tipos de animación construidos exclusivamente con transform y opacity, keyframes namespaced y delays escalonados — cópialos como referencia de animación limpia. Para profundizar en la mecánica interna de cada spinner, tienes la guía de spinners CSS, y si tu animación implica desenfoques, revisa los costes reales de backdrop-filter en la guía glassmorphism.

Preguntas frecuentes

¿60 fps o 120 fps? Objetivo razonable: mantener 60 estable. En pantallas ProMotion el objetivo sube a 120 (presupuesto de 8,3 ms/frame) — otro motivo para ceñirse a transform/opacity.

¿Animar SVG es distinto? Los mismos principios aplican, con matiz: atributos geométricos SVG (cx, r) fuerzan layout del gráfico; usar transform CSS sobre el nodo sigue siendo la vía barata.

¿Las librerías (GSAP, Framer Motion) ya optimizan? Las buenas sí — priorizan transforms y batchean. Pero ninguna puede convertir un width animado en composite-only: la elección de propiedad sigue siendo tuya.


Genera animaciones limpias listas para producción con nuestro generador de Loaders CSS, gratis y directamente en tu navegador.

Pruébalo sin código

Generador de Loaders CSS

Spinners animados en CSS puro.

Abrir Generador de Loaders CSS

Hecho por

Miguel Ángel Colorado Marin (MACM)

Full-Stack Developer · Guadalajara, España

Desarrollo aplicaciones web, herramientas digitales y proyectos completos — desde el diseño hasta el despliegue.

Contáctame