Leer y escribir alternado
Medir y escribir en el mismo bucle fuerza al navegador a hacer reflow N veces. Separar fases lo rompe.
- layout thrashing
- reflow forzado síncrono
- scrollHeight
El problema
Tienes varios resúmenes en una página. Quieres añadir un botón “Leer
más” a los que tengan texto oculto por overflow. Lo obvio: recorrer
todos, comprobar si scrollHeight > clientHeight (que indica que hay
texto que no cabe), y si es así, crear el botón e insertarlo.
Funciona. Y en 30 resúmenes se nota que va lento, aunque el código
parece trivial. El problema tiene nombre: layout thrashing. Leer
propiedades como scrollHeight obliga al navegador a recalcular
layout ahí mismo, en medio de tu script, antes de devolverte el
número. Si has escrito al DOM justo antes (insertar un botón cuenta),
esa escritura invalida el layout previo, y la próxima lectura fuerza
otro recálculo. Uno por vuelta. En 30 iteraciones, 30 reflows.
El código “roto”
const resumenes = document.querySelectorAll(".resumen");
resumenes.forEach((resumen) => {
if (resumen.scrollHeight > resumen.clientHeight) {
const boton = document.createElement("button");
boton.textContent = "Leer más";
boton.className = "leer-mas";
resumen.parentElement.appendChild(boton);
}
});
Las pistas
- Todas las lecturas primero, todas las escrituras después.
- Guarda en un array los nodos que necesiten botón durante la fase de lectura.
- El navegador puede servir un lote de lecturas con un solo reflow.
Ver la solución
const resumenes = document.querySelectorAll(".resumen");
// Lecturas
const necesitanBoton = [];
resumenes.forEach((resumen) => {
// mide aquí y guarda en el array los que tienen texto oculto
if (resumen.scrollHeight > resumen.clientHeight) {
necesitanBoton.push(resumen);
}
});
// Escrituras
necesitanBoton.forEach((resumen) => {
// crea e inserta aquí el botón
const boton = document.createElement("button");
boton.textContent = "Leer más";
boton.className = "leer-mas";
resumen.parentElement.appendChild(boton);
});Dos fases, y el ciclo se rompe. En la primera solo hay lecturas
(scrollHeight), y el navegador puede servir todas con un único
reflow al principio, sin nada que invalide layout entre ellas. En la
segunda solo hay escrituras (createElement + appendChild), y ninguna
lectura fuerza al navegador a “detente y recalcula ya”. Las
mutaciones se acumulan y el navegador las aplica cuando el script
termina.
Propiedades que fuerzan reflow síncrono al leerlas: offsetWidth,
offsetHeight, offsetTop, offsetLeft, scrollWidth,
scrollHeight, scrollTop, scrollLeft, clientWidth,
clientHeight, getBoundingClientRect(), getComputedStyle(). Si
las llamas después de haber tocado el DOM en el mismo tick, pagas
un reflow. Si las llamas todas juntas antes de tocar nada, pagas uno.
Este patrón — leer todo, luego escribir todo — es la base de muchas
librerías de animación y de virtualización de listas. Vale la pena
tenerlo en la cabeza cada vez que un bucle mezcla getBoundingClientRect
con appendChild.
Alternar lectura + escritura vs dos fases separadas
El script se ejecuta de golpe sobre N resúmenes con texto cortado por overflow. En modo alternado, cada iteración leescrollHeight (forzando reflow tras la escritura anterior) y después inserta un botón Leer más: N reflows forzados. En mododos fases, primero se leen todos los scrollHeight (un solo reflow al empezar) y después se escriben todos los botones sin lecturas intercaladas.
Comprueba lo que has aprendido
1. ¿Qué es un “reflow forzado síncrono”?
2. ¿Cuál de estas propiedades fuerza reflow síncrono al leerse?
3. ¿Qué es el “layout thrashing”?
4. ¿Por qué la solución separa lecturas y escrituras en dos bucles?
5. En un bucle con 30 elementos que lee `scrollHeight` y crea un botón cada vez, ¿cuántos reflows fuerza en el peor caso?