Verificar la composición en las herramientas
Los cuatro instrumentos que responden a la pregunta de si una animación está compuesta, qué mide cada uno y cuál usar en cada situación.
Preguntarle a alguien si una animación está compuesta es un mal método: la respuesta depende del motor, de la versión, del dispositivo y de lo que haya en el resto de la página. La pregunta es empírica y tiene cuatro instrumentos capaces de contestarla, cada uno con una precisión y un coste distintos. Esta lección los ordena por el momento en que conviene usar cada uno, desde la comprobación de cinco segundos hasta la traza que te da el motivo exacto de una degradación.
- Elegir el instrumento adecuado según la pregunta que tengas.
- Interpretar el panel de capas y las razones de composición.
- Leer una traza de rendimiento e identificar dónde falta trabajo, no dónde sobra.
- Montar una comprobación reproducible que puedas repetir en cada cambio.
Los cuatro instrumentos
Instrumento 1: la prueba del bloqueo
Es la única prueba binaria y no requiere abrir nada. Bloqueas el hilo principal y miras si la animación sigue.
// Pegar en la consola. Bloquea 1.5 segundos tras medio segundo de margen.
setTimeout(() => {
const fin = performance.now() + 1500;
while (performance.now() < fin) { /* ocupado */ }
}, 500);
Qué contesta: si la animación está en el compositor, sí o no. Sin matices y sin depender de que interpretes bien un gráfico.
Qué no contesta: por qué no lo está, ni si el coste que sí paga es aceptable.
Es la primera que hay que hacer siempre, porque descarta o confirma la hipótesis en cinco segundos, y porque las otras tres son mucho más lentas de interpretar. Un error frecuente es empezar por la traza de rendimiento y perder veinte minutos analizando un fotograma cuando la respuesta estaba a un while de distancia.
Instrumento 2: el panel de renderizado
Es el instrumento de vigilancia continua: lo dejas activado mientras desarrollas y te avisa de problemas que no estabas buscando. Tres opciones importan aquí.
Destello de repintado. Colorea las regiones que se repintan. Una animación compuesta no produce ningún destello. Si tu animación pinta un rectángulo verde en cada fotograma, no está compuesta, y además el tamaño del rectángulo te dice cuánta área estás repintando, que es el dato que faltaba en la lección de rasterizado.
Bordes de capa. Dibuja el contorno de cada capa de composición. Es la forma más rápida de comprobar si tu elemento tiene la suya y de detectar promociones implícitas: si al añadir un will-change aparecen quince contornos nuevos, has arrastrado a quince elementos.
Estadísticas de fotogramas. Muestra en una esquina la tasa de fotogramas actual y la de la GPU. Sirve para lo que ninguna traza sirve: ver el efecto en tiempo real mientras tocas valores.
La combinación que más rinde es destello de repintado más bordes de capa, activados a la vez. Con las dos, una animación bien construida se ve como un contorno que se mueve sobre un fondo que no destella; una mal construida, como una mancha verde que persigue al elemento.
Instrumento 3: el panel de capas
Cuando la pregunta es “por qué este elemento no tiene capa” o “de dónde han salido estas cuarenta capas”, el panel de capas es el único que contesta. Muestra el árbol de capas, el tamaño de cada una, su consumo de memoria y —lo más valioso— las razones de composición: por qué existe cada capa.
Esas razones son texto explícito, del tipo “tiene una animación de transformación acelerada” o “se solapa con otra capa compuesta”. La segunda es la firma de la promoción implícita, y encontrarla en un elemento que tú no promoviste te dice inmediatamente que el problema está en un vecino.
El uso más productivo del panel no es diagnóstico, es preventivo: mirar el consumo total de memoria de capas antes de dar por buena una optimización. Si has añadido will-change a los elementos de una lista y el total ha pasado de tres megabytes a ochenta, has empeorado la página aunque cada animación individual haya mejorado.
Instrumento 4: la traza de rendimiento
Es el instrumento con más información y el más lento de leer, así que se usa el último, cuando ya sabes que hay un problema y necesitas la causa exacta.
Tres cosas hay que mirar, y en este orden.
La pista de animaciones. En Chromium, las animaciones que no se han podido componer aparecen marcadas con un triángulo rojo, y al seleccionarlas el resumen indica el motivo. Ese motivo mapea directamente a los casos de la lección anterior y ahorra toda la deducción.
La ausencia de trabajo en el hilo principal. Esto es lo contraintuitivo de leer trazas de animación: buscas dónde falta trabajo, no dónde sobra. Durante una animación compuesta correcta, el hilo principal está vacío y la actividad está en las pistas del compositor y del rasterizador. Si ves recálculo de estilo, layout o pintado repitiéndose fotograma a fotograma, has encontrado el problema sin necesidad de leer ningún número.
Los fotogramas largos. La pista de fotogramas colorea los que se han salido de presupuesto. Lo que interesa no es cuántos hay sino si están agrupados: unos pocos al principio suelen ser el coste de crear la capa, y se arreglan con will-change anticipado; repartidos por toda la animación indican trabajo por fotograma.
Y una recomendación que cambia el resultado del análisis: graba siempre con la ralentización de CPU activada, a cuatro o seis veces. En una máquina de desarrollo moderna, una animación mal construida puede caber en el presupuesto y no mostrar ningún síntoma. La ralentización convierte diferencias invisibles en diferencias medibles y aproxima el dispositivo de tus usuarios.
Hay un sesgo en la forma en que todo el mundo mide animaciones y produce optimizaciones que no sirven para nada en producción. Cuando abres las herramientas, grabas y disparas la animación, casi nunca es la primera vez que se ejecuta: la has probado antes, las capas ya están creadas, las texturas ya están rasterizadas, las fuentes ya están cargadas y el código ya está compilado por el motor de JavaScript. Estás midiendo la segunda ejecución en adelante, que es la barata. La primera es otra cosa completamente distinta: hay que crear la capa, rasterizar su contenido por primera vez, y si hay una fuente o una imagen implicadas, esperarlas. Es habitual que la primera ejecución cueste diez o veinte veces más que las siguientes, y es la única que muchos usuarios van a ver, porque abren un menú una vez y se van. La forma de medirla es recargar con la caché desactivada, empezar a grabar antes de que la página termine de cargar y disparar la animación una sola vez. Lo que aparece ahí no se parece a lo que aparece en la décima repetición, y es lo que hay que optimizar. Esto además reordena las prioridades: si la primera ejecución domina, el problema no es la curva ni la propiedad, es el coste de creación de capa y de rasterización inicial, y las soluciones son otras —promover con antelación durante un momento de inactividad, precargar la fuente, reservar el espacio— ninguna de las cuales aparece en una traza de la décima repetición.
Una comprobación reproducible
Para que la verificación no dependa de acordarse, conviene tener un procedimiento fijo que se pueda repetir en cada cambio. Cuatro pasos, tres minutos.
Uno. Recarga sin caché, dispara la animación una sola vez con el destello de repintado activado. Anota si destella y el tamaño de la región.
Dos. Ejecuta el bloqueo del hilo principal durante la animación. Anota si sobrevive.
Tres. Abre el panel de capas y anota el número de capas y la memoria total, antes y después de tu cambio.
Cuatro. Graba una traza con ralentización a seis veces y anota el peor fotograma y si hay trabajo repetido en el hilo principal.
Los cuatro números —destella o no, sobrevive o no, memoria de capas, peor fotograma— son los que hay que anotar en la descripción de un cambio de rendimiento. Sin ellos, “he optimizado la animación” no es una afirmación verificable.
Para el último número hay una versión automatizable que sirve como red de seguridad en integración continua:
// Devuelve el peor tiempo de fotograma durante una accion.
async function peorFotograma(accion, ms = 2000) {
const tiempos = [];
let anterior = null;
const listo = new Promise((r) => setTimeout(r, ms));
const paso = (t) => {
if (anterior !== null) tiempos.push(t - anterior);
anterior = t;
requestAnimationFrame(paso);
};
requestAnimationFrame(paso);
await accion();
await listo;
return Math.max(...tiempos);
}
Comparar ese número entre dos ramas es imperfecto —depende de la máquina— pero una regresión de un factor de cinco se ve por encima de cualquier ruido, y eso es lo que quieres atrapar.
- Aplica los cuatro pasos del procedimiento a una animación de tu proyecto y anota los cuatro números.
- Rómpela a propósito añadiendo una propiedad no componible y repite. Comprueba que los cuatro cambian de forma coherente.
- Mide la primera ejecución con la caché desactivada y compárala con la décima. Anota el factor.
- Decide, con esos datos, si tu optimización debería centrarse en el coste por fotograma o en el coste de arranque.