O DOM é lento. Não por natureza, mas porque cada leitura e escrita força o browser a recalcular layouts. O problema clássico é Layout Thrashing.
O que é Layout Thrashing
// ❌ 1000 reflows forçados
for (let i = 0; i < 1000; i++) {
const w = element.offsetWidth; // lê → força layout
element.style.width = w + 1 + "px"; // escreve → invalida
}
Cada offsetWidth força o browser a recalcular o layout na hora pra te dar um valor correto. Faça isso num loop e sua UI congela.
Batch reads + writes
A solução é separar leitura de escrita em dois batches:
// ✅ Lê tudo primeiro
const widths = elements.map((el) => el.offsetWidth);
// ✅ Depois escreve tudo
elements.forEach((el, i) => {
el.style.width = widths[i] + 1 + "px";
});
Propriedades que disparam reflow
As mais comuns: offsetHeight, offsetWidth, getBoundingClientRect(), scrollTop, clientTop, computedStyle.
requestAnimationFrame
Use rAF para agrupar writes no próximo frame visual:
requestAnimationFrame(() => {
el.style.transform = `translateX(${x}px)`;
});
Virtual Scrolling
Para listas com centenas de itens, o DOM real precisa ser menor que o dataset. Libraries como TanStack Virtual só renderizam os itens visíveis + um buffer.
// Conceito: windowed rendering
const visibleItems = allItems.slice(start, end);
// Só N elementos no DOM, não 10.000
Meça antes de otimizar. Um
console.timemuitas vezes revela que o gargalo não está onde você acha.