Front-end · Pleno

Renderização e performance: reflow, repaint e o caminho crítico

"Por que essa página trava quando rolo a lista?" Quase sempre a resposta está em como o navegador transforma seu HTML e CSS em pixels — o caminho crítico de renderização. Entender reflow e repaint é o que separa quem só faz a tela aparecer de quem faz a tela aparecer rápido.

O caminho crítico de renderização

Para desenhar um pixel, o navegador segue uma esteira de etapas:

HTML  → DOM ┐
            ├→ Render Tree → Layout → Paint → Composite
CSS   → CSSOM┘

O DOM (estrutura) e o CSSOM (estilos) se combinam na render tree (o que é visível e como). O Layout calcula posição e tamanho de cada elemento, o Paint preenche os pixels (cor, texto, sombra) e o Composite junta as camadas na tela.

Reflow vs repaint

Quando algo muda, o navegador não refaz tudo. Existem dois níveis de retrabalho:

  • Reflow (layout): recalcular geometria — posição e tamanho. Caro, porque pode afetar elementos vizinhos e até a página inteira.
  • Repaint: redesenhar pixels sem mexer na geometria — mudar uma cor de fundo, por exemplo. Mais barato que reflow.

Analogia: reflow é remontar a planta da casa (mexe nas paredes); repaint é só repintar uma parede que já existe.

Reflow (layout)Repaint
Recalcula geometria?SimNão
CustoAltoMédio
Dispara comwidth, top, font-size, add/remove nócolor, background, visibility

O que dispara reflow

Mudar dimensões, posição, fonte, adicionar ou remover elementos, redimensionar a janela — tudo força reflow. E há um detalhe traiçoeiro: ler certas propriedades (como offsetHeight, getBoundingClientRect) força o navegador a fazer um reflow na hora para te dar o valor atualizado.

Layout thrashing: o vilão silencioso

O layout thrashing acontece quando você intercala leituras e escritas num loop, forçando reflow repetido. O navegador queria agrupar as mudanças, mas cada leitura o obriga a recalcular imediatamente.

// RUIM: lê e escreve alternado → reflow a cada volta
for (const el of itens) {
  el.style.height = el.offsetHeight + 10 + 'px';
}

// BOM: lê tudo, depois escreve tudo
const alturas = itens.map(el => el.offsetHeight);
itens.forEach((el, i) => el.style.height = alturas[i] + 10 + 'px');

O que fazer na prática

  • Agrupe leituras e escritas: leia todas as medidas, depois aplique todas as mudanças.
  • Anime com transform e opacity: elas rodam no composite, sem reflow nem repaint.
  • Evite forçar layout síncrono: não leia offsetTop logo após escrever um estilo.
  • Virtualize listas longas: renderize só o que está visível (windowing) em vez de 10.000 linhas.
  • Use will-change com parcimônia para promover elementos animados a camadas próprias.
A renderização rápida raramente vem de "deixar o JS mais rápido" — vem de pedir menos trabalho de layout ao navegador.

É comum em entrevista pedir para você explicar a diferença entre reflow e repaint, ou por que animar transform é melhor que animar top. No dia a dia, isso aparece quando uma lista grande engasga ao rolar ou uma animação fica travada.

Conclusão

O navegador segue DOM → CSSOM → layout → paint → composite, e cada mudança pode custar um reflow (caro) ou um repaint (mais barato). Agrupe leituras e escritas, anime no composite e virtualize listas. Esse cuidado local é a base para a visão mais ampla de Core Web Vitals.

"Why does this page lag when I scroll the list?" Almost always the answer lies in how the browser turns your HTML and CSS into pixels — the critical rendering path. Understanding reflow and repaint is what separates those who just make the screen appear from those who make it appear fast.

The critical rendering path

To draw a pixel, the browser follows an assembly line of steps:

HTML  → DOM ┐
            ├→ Render Tree → Layout → Paint → Composite
CSS   → CSSOM┘

The DOM (structure) and the CSSOM (styles) combine into the render tree (what is visible and how). Layout calculates the position and size of each element, Paint fills in the pixels (color, text, shadow) and Composite stacks the layers on screen.

Reflow vs repaint

When something changes, the browser doesn't redo everything. There are two levels of rework:

  • Reflow (layout): recalculating geometry — position and size. Expensive, because it can affect neighboring elements and even the entire page.
  • Repaint: redrawing pixels without touching geometry — changing a background color, for example. Cheaper than reflow.

Analogy: reflow is redrawing the house's floor plan (it moves walls); repaint is just repainting a wall that already exists.

Reflow (layout)Repaint
Recalculates geometry?YesNo
CostHighMedium
Triggered bywidth, top, font-size, add/remove nodecolor, background, visibility

What triggers reflow

Changing dimensions, position, font, adding or removing elements, resizing the window — everything forces reflow. And there's a treacherous detail: reading certain properties (like offsetHeight, getBoundingClientRect) forces the browser to do a reflow right then to give you the up-to-date value.

Layout thrashing: the silent villain

Layout thrashing happens when you interleave reads and writes in a loop, forcing repeated reflow. The browser wanted to batch the changes, but each read forces it to recalculate immediately.

// BAD: alternating read and write → reflow on every iteration
for (const el of itens) {
  el.style.height = el.offsetHeight + 10 + 'px';
}

// GOOD: read everything, then write everything
const alturas = itens.map(el => el.offsetHeight);
itens.forEach((el, i) => el.style.height = alturas[i] + 10 + 'px');

What to do in practice

  • Batch reads and writes: read all measurements, then apply all changes.
  • Animate with transform and opacity: they run at the composite step, with no reflow or repaint.
  • Avoid forcing synchronous layout: don't read offsetTop right after writing a style.
  • Virtualize long lists: render only what's visible (windowing) instead of 10,000 rows.
  • Use will-change sparingly to promote animated elements to their own layers.
Fast rendering rarely comes from "making the JS faster" — it comes from asking the browser for less layout work.

Interviews commonly ask you to explain the difference between reflow and repaint, or why animating transform is better than animating top. Day to day, it shows up when a large list stutters on scroll or an animation gets janky.

Conclusion

The browser follows DOM → CSSOM → layout → paint → composite, and each change can cost a reflow (expensive) or a repaint (cheaper). Batch reads and writes, animate at the composite step and virtualize lists. This local care is the basis for the broader view of Core Web Vitals.

Leia também

Voltar à trilha