Renderizado · 2 min de lectura

Primero leer, luego escribir: evitar el layout thrashing

Mezclar lecturas y escrituras del DOM en un mismo bucle obliga al navegador a recalcular el layout una y otra vez. Mejor agruparlas.

Cuando cambias un estilo, el navegador no recalcula el layout de inmediato. Lo marca como desactualizado y espera al siguiente fotograma. Pero si después lees un valor de geometría como offsetWidth, no le queda otra: tiene que recalcular el layout en ese momento para darte una respuesta correcta. Si esto ocurre dentro de un bucle, tienes layout thrashing.

La versión lenta

for (const el of items) {
  const width = el.parentNode.offsetWidth; // lectura: fuerza el layout
  el.style.width = width / 2 + 'px';       // escritura: invalida el layout
}

Cada iteración invalida el layout y la siguiente lo vuelve a forzar. Con unos cientos de elementos, esto puede costar fácilmente decenas de milisegundos.

La versión rápida

const widths = items.map((el) => el.parentNode.offsetWidth); // todas las lecturas
items.forEach((el, i) => {                                     // todas las escrituras
  el.style.width = widths[i] / 2 + 'px';
});

Mismo resultado, un solo layout. La regla es sencilla: primero mide todo lo que necesites y después cambia cosas.

Arriba se alternan lecturas y escrituras, y cada lectura fuerza un layout nuevo. Abajo van primero todas las lecturas y después todas las escrituras: el layout se calcula una sola vez.JavaScriptLayout

Qué cuenta como lectura

Los sospechosos habituales son las propiedades y métodos que devuelven geometría o valores calculados:

Detectarlo

En el panel Performance de las herramientas de desarrollo, el thrashing aparece como muchos bloques morados pequeños de Layout dentro de una sola tarea, a menudo con un aviso de forced reflow que señala la línea de código responsable. Si el código tiene que reaccionar a cambios de tamaño, un ResizeObserver suele ser mejor opción que medir a mano.

Dónde encaja el layout dentro de un fotograma lo explica Un fotograma, 16,7 milisegundos: el pipeline de renderizado.

← Todas las notas