Estadísticas de fotogramas y problemas de desplazamiento
El contador de fotogramas y qué significan sus números, el overlay que marca las regiones que estorban al desplazamiento, y cómo se distingue un tirón de compositor de uno de hilo principal.
Un tirón al desplazarse es de las quejas más difíciles de convertir en un dato, porque desaparece en cuanto se intenta medir: grabar un perfil añade sobrecarga, el gesto no se reproduce igual dos veces, y el problema depende del hardware. Dos superposiciones del panel de renderizado atacan justamente ese caso: una da la cadencia real de fotogramas en directo, y otra marca las zonas de la página que impiden que el desplazamiento lo maneje el compositor.
- Leer el contador de estadísticas de fotogramas y sus tres bloques de información.
- Interpretar la cadencia real frente a la que el dispositivo puede alcanzar.
- Identificar las regiones que devuelven el desplazamiento al hilo principal.
- Distinguir un tirón de composición de uno de hilo principal en menos de un minuto.
Las estadísticas de fotogramas
La casilla correspondiente del panel de renderizado superpone un pequeño panel en la esquina de la página con tres bloques.
La cadencia de fotogramas instantánea, con su valor actual, su mínimo y su máximo recientes, más un pequeño gráfico. Es la medida directa de fluidez.
El estado de la rasterización acelerada, que dice si la GPU está participando en el dibujo o si el navegador está cayendo a rasterización por software. Este dato salva investigaciones enteras: si la aceleración está desactivada, ninguna optimización de capas va a servir de nada y la causa está en el sistema, en el controlador o en un indicador de línea de comandos.
La memoria de GPU en uso, que es la comprobación empírica de lo que la lección anterior estimaba a mano. Si esa cifra crece sin parar mientras usas la página, hay capas que se crean y no se destruyen.
El objetivo no es siempre sesenta fotogramas por segundo. Muchos dispositivos modernos presentan a ciento veinte, y en ellos el presupuesto por fotograma se reduce a la mitad: unos ocho milisegundos en lugar de dieciséis. Y en el otro extremo, un dispositivo modesto puede estar limitado a treinta. La cifra que importa no es un número absoluto sino si la cadencia real coincide con la que el dispositivo puede alcanzar, y si se mantiene estable. Sesenta constantes se perciben mejor que una alternancia entre noventa y cuarenta.
Regiones que estorban al desplazamiento
La otra casilla resalta las áreas de la página donde el desplazamiento no puede resolverse solo en el compositor. El navegador tiene que consultar al hilo principal antes de desplazar esas zonas, y si el hilo está ocupado, el desplazamiento espera. Es exactamente el mecanismo que produce la sensación de que la página “se pega”.
Lo que se marca son dos cosas.
Regiones con manejadores de rueda o de toque no pasivos. Cuando un elemento tiene un escucha de estos eventos sin declararse pasivo, el navegador no puede saber si ese manejador va a cancelar el gesto, así que tiene que ejecutarlo antes de desplazar. Esa consulta convierte cada movimiento del dedo en una espera al hilo principal. La solución es declarar el escucha como pasivo, que es una promesa de que no se va a cancelar el evento.
// Un escucha pasivo permite que el desplazamiento no espere al hilo principal
const contenedor = document.querySelector('main') || document.body;
// Mal: el navegador tiene que preguntar antes de desplazar
contenedor.addEventListener('touchstart', alTocar);
// Bien: promete que no se va a cancelar, el compositor sigue por su cuenta
contenedor.addEventListener('touchstart', alTocar, { passive: true });
contenedor.addEventListener('wheel', alGirar, { passive: true });
function alTocar(e) { /* no llama a preventDefault */ }
function alGirar(e) { /* no llama a preventDefault */ }
// Auditoria rapida: encuentra escuchas no pasivos en los eventos criticos
(() => {
const criticos = ['touchstart', 'touchmove', 'wheel', 'mousewheel'];
const original = EventTarget.prototype.addEventListener;
const sospechosos = [];
EventTarget.prototype.addEventListener = function (tipo, fn, opciones) {
if (criticos.includes(tipo)) {
const pasivo = opciones && typeof opciones === 'object' ? opciones.passive : undefined;
if (pasivo !== true) {
sospechosos.push({ tipo, destino: this?.tagName || String(this), pila: new Error().stack.split('\n')[2]?.trim() });
}
}
return original.call(this, tipo, fn, opciones);
};
window.escuchasNoPasivos = () => { console.table(sospechosos); return sospechosos; };
console.log('Interceptando registros. Recarga o navega y ejecuta escuchasNoPasivos()');
})();
Regiones que no pueden componerse de forma independiente. Fondos fijos, ciertos efectos, contenedores anidados con desplazamiento propio en situaciones que el compositor no sabe resolver por su cuenta.
Distinguir los dos tipos de tirón
Un tirón al desplazarse tiene dos orígenes muy distintos, y el diagnóstico se hace en menos de un minuto con una sola comprobación.
Prueba: bloquea el hilo principal a propósito y desplázate.
// Bloquea el hilo principal durante tres segundos.
// Si durante ese tiempo la pagina se desplaza, el desplazamiento
// lo lleva el compositor y el problema esta en otro sitio.
setTimeout(() => {
const t = performance.now();
console.log('Bloqueando el hilo principal. Intenta desplazarte ahora.');
while (performance.now() - t < 3000) {}
console.log('Desbloqueado.');
}, 1500);
Si durante el bloqueo la página se desplaza normalmente, el desplazamiento lo maneja el compositor y el tirón que investigas no viene del hilo principal: viene del propio compositor, de demasiadas capas, de superficies enormes o de rasterización lenta. La investigación sigue con los bordes de capa y la memoria de GPU.
Si durante el bloqueo la página no se desplaza en absoluto, el desplazamiento depende del hilo principal, y ahí está el problema: hay manejadores no pasivos o efectos calculados en JavaScript. La investigación sigue con el overlay de regiones problemáticas y la auditoría de escuchas.
Esa bifurcación divide el espacio de causas en dos mitades sin herramientas en común, y cuesta cinco segundos.
Medir la fluidez sin el overlay
En un dispositivo remoto o cuando hace falta un número que registrar, este medidor da la distribución de duraciones de fotograma, que es más informativa que una media.
// Distribucion de duraciones de fotograma durante una interaccion
function medirFluidez(segundos = 5) {
return new Promise(resolver => {
const duraciones = [];
let anterior = performance.now();
const inicio = anterior;
const paso = ahora => {
duraciones.push(ahora - anterior);
anterior = ahora;
if (ahora - inicio < segundos * 1000) requestAnimationFrame(paso);
else {
const ordenadas = duraciones.slice().sort((a, b) => a - b);
const p = q => ordenadas[Math.floor(ordenadas.length * q)] || 0;
const largos = duraciones.filter(d => d > 32).length;
console.table([{
fotogramas: duraciones.length,
medianaMs: +p(0.5).toFixed(1),
p95Ms: +p(0.95).toFixed(1),
peorMs: +ordenadas.at(-1).toFixed(1),
fotogramasLargos: largos,
porcentajeLargos: +(100 * largos / duraciones.length).toFixed(1)
}]);
resolver(duraciones);
}
};
console.log('Desplazate o interactua durante', segundos, 'segundos.');
requestAnimationFrame(paso);
});
}
await medirFluidez(5);
El percentil noventa y cinco y el porcentaje de fotogramas largos son las dos cifras que se corresponden con la percepción. Una mediana de dieciséis milisegundos con un percentil noventa y cinco de ciento veinte describe una experiencia mala que una media aritmética disimularía por completo.
Hay una razón perceptiva por la que todos los indicadores de fluidez basados en promedios engañan, y conviene entenderla porque determina qué se debe medir y qué objetivo ponerse. El sistema visual humano detecta discontinuidades, no cadencias. Una secuencia de fotogramas perfectamente regulares a treinta por segundo se percibe como suave; una secuencia a sesenta con un fotograma de setenta milisegundos cada dos segundos se percibe como defectuosa, aunque su media sea el doble de buena. Lo que se nota es la ruptura de la regularidad, no la velocidad. De ahí salen tres consecuencias que cambian el trabajo. La primera: un contador de fotogramas por segundo es un instrumento pobre, porque promedia sobre una ventana y precisamente destruye la información que importa. Un valor de cincuenta y ocho puede corresponder a una experiencia perfecta o a una llena de tirones, y no hay forma de distinguirlas mirando ese número. Lo que hay que mirar es la distribución, y en particular la cola. La segunda: estabilizar es mejor que acelerar. Si tu animación no llega a la cadencia máxima del dispositivo de forma consistente, muchas veces la mejor decisión no es intentar optimizarla hasta llegar sino reducir deliberadamente su ambición para que sea regular. Una animación más sencilla que nunca falla un fotograma se percibe mejor que una elaborada que falla uno de cada diez. La tercera, y la que más se olvida: el peor momento es el que define el recuerdo. Un desplazamiento que va perfecto durante tres segundos y se atasca medio segundo al llegar a una sección concreta se recuerda como “la página va a tirones”, no como “la página va bien el ochenta y cinco por ciento del tiempo”. Por eso el trabajo rentable no está en subir la media sino en encontrar y eliminar los casos peores, que suelen ser pocos, concretos y provocados por una causa identificable: una imagen enorme que se decodifica al entrar en la vista, un componente que se monta al llegar a cierta altura, una capa gigante que se rasteriza por primera vez. Buscar la cola es un método distinto de buscar el promedio, y el overlay de esta lección, con su mínimo visible, es una de las pocas herramientas que la enseña directamente.