"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? | Sim | Não |
| Custo | Alto | Médio |
| Dispara com | width, 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
transformeopacity: elas rodam no composite, sem reflow nem repaint. - Evite forçar layout síncrono: não leia
offsetToplogo após escrever um estilo. - Virtualize listas longas: renderize só o que está visível (windowing) em vez de 10.000 linhas.
- Use
will-changecom 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? | Yes | No |
| Cost | High | Medium |
| Triggered by | width, top, font-size, add/remove node | color, 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
transformandopacity: they run at the composite step, with no reflow or repaint. - Avoid forcing synchronous layout: don't read
offsetTopright after writing a style. - Virtualize long lists: render only what's visible (windowing) instead of 10,000 rows.
- Use
will-changesparingly 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.