Portal Performance
dom

Leer y escribir alternado

Medir y escribir en el mismo bucle fuerza al navegador a hacer reflow N veces. Separar fases lo rompe.

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

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.

Pruébalo

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.

Alternado · scrollHeight y appendChild en cada vuelta
Pulsa Ejecutar para medir
Dos fases · todo leer, luego todo escribir
Pulsa Ejecutar para medir

Comprueba lo que has aprendido

  1. 1. ¿Qué es un “reflow forzado síncrono”?

  2. 2. ¿Cuál de estas propiedades fuerza reflow síncrono al leerse?

  3. 3. ¿Qué es el “layout thrashing”?

  4. 4. ¿Por qué la solución separa lecturas y escrituras en dos bucles?

  5. 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?