Performance de Renderização no Browser: Reflow, Repaint, Compositing e content-visibility

Aprenda a eliminar jank e construir interfaces fluidas a 60fps dominando o pipeline de renderização do browser — reflow, repaint, compositing, CSS containment e content-visibility com exemplos práticos.

Introdução — Quando o Browser Trabalha Demais

Você otimizou imagens, implementou code splitting, colocou fetchpriority no hero — e mesmo assim o site parece engasgar ao fazer scroll ou ao clicar num botão. A página carrega rápido, mas a interação é lenta e travada. Se isso soa familiar, bem-vindo ao clube. O problema provavelmente não está na rede. Está no pipeline de renderização do browser.

Honestamente, a performance de renderização é o elo que quase todo mundo ignora. É ela que determina se o seu site parece fluido a 60fps ou se tem aquele "jank" irritante que faz o utilizador desistir antes de converter. E os números não mentem: segundo o Web Almanac de 2025, 40% das páginas mobile ainda utilizam animações não compostas — basicamente forçando o browser a recalcular layout a cada frame, quando podiam simplesmente delegar o trabalho à GPU.

Neste guia, vamos percorrer todo o pipeline de renderização — de reflow a compositing — com técnicas concretas e código que você pode usar amanhã mesmo. Se já leu o nosso guia de INP, este artigo é o complemento natural: enquanto o INP mede a responsividade, aqui vamos entender por que as interações ficam lentas e como corrigi-las de verdade.

O Pipeline de Renderização — Como os Pixels Chegam ao Ecrã

Cada vez que algo muda visualmente numa página — um clique, um scroll, uma animação — o browser precisa atualizar o ecrã. Este processo segue um pipeline com etapas sequenciais:

  1. JavaScript/CSS → Uma alteração é disparada (um evento, uma animação CSS, uma manipulação do DOM)
  2. Style → O browser calcula que estilos se aplicam a cada elemento
  3. Layout (Reflow) → Calcula a geometria — posição, tamanho e relação entre elementos
  4. Paint (Repaint) → Preenche os pixels — cores, sombras, bordas, texto
  5. Composite → Combina as camadas gráficas e envia o resultado final ao ecrã

E aqui está o ponto que muda tudo: nem toda alteração precisa passar por todas as etapas. Alterar a cor de fundo pula o Layout. Alterar transform ou opacity pula tanto o Layout quanto o Paint, indo diretamente ao Composite. Quanto mais etapas o browser conseguir saltar, mais rápida é a atualização.

A 60fps, o browser tem apenas 16,66ms por frame para executar todo este pipeline. Descontando o overhead interno, sobram cerca de 10ms para o seu código. Dez milissegundos. Ultrapasse esse orçamento e o utilizador vai sentir — frames são descartados, animações engasgam, e a página parece aquelas interfaces dos anos 2000.

Reflow (Layout) — O Vilão Silencioso da Performance

O reflow (ou layout) acontece quando o browser precisa recalcular a posição e geometria dos elementos. É a operação mais cara do pipeline porque é cascata — alterar o tamanho de um único elemento pode forçar o recálculo de filhos, irmãos e até pais.

Já vi páginas onde um simples toggle de sidebar causava reflow de 300+ elementos. O utilizador clicava e sentia aquele micro-travamento que parece "nada" mas destrói a percepção de qualidade.

O que dispara um reflow?

Qualquer alteração que afete a geometria de um elemento vai disparar reflow:

  • Alterar width, height, padding, margin, border
  • Alterar top, left, right, bottom (em elementos posicionados)
  • Alterar font-size, line-height ou font-family
  • Adicionar ou remover elementos do DOM
  • Alterar display, position ou float
  • Redimensionar a janela do browser

Mas atenção — e isto é algo que pega muita gente de surpresa — ler certas propriedades também força reflow. Propriedades como offsetWidth, offsetHeight, getBoundingClientRect(), scrollTop e clientHeight forçam o browser a recalcular o layout para devolver valores atualizados. Só por ler.

Como minimizar reflows

1. Agrupe alterações de estilo:

// ❌ Mau — 3 reflows sequenciais
element.style.width = '100px';
element.style.height = '200px';
element.style.margin = '10px';

// ✅ Bom — 1 único reflow com cssText ou classe CSS
element.style.cssText = 'width: 100px; height: 200px; margin: 10px;';

// ✅ Melhor ainda — toggle de classe
element.classList.add('nova-dimensao');

2. Use DocumentFragment para inserções em massa:

// ❌ Mau — reflow a cada iteração
for (const item of items) {
  const li = document.createElement('li');
  li.textContent = item.name;
  list.appendChild(li); // reflow!
}

// ✅ Bom — um único reflow no final
const fragment = document.createDocumentFragment();
for (const item of items) {
  const li = document.createElement('li');
  li.textContent = item.name;
  fragment.appendChild(li);
}
list.appendChild(fragment); // um único reflow

3. Prefira Flexbox a floats: O layout Flexbox é aproximadamente 4× mais rápido que layout baseado em floats para o mesmo número de elementos. Se ainda está usando floats para layouts complexos (e sim, em 2026 ainda encontro isso em produção), migrar para Flexbox ou Grid é provavelmente a melhoria mais simples e impactante que pode fazer.

Repaint — Mais Leve, Mas Não Inofensivo

O repaint acontece quando a aparência de um elemento muda sem afetar a sua geometria. O browser redesenha os pixels — sem recalcular posições, o que torna o repaint mais leve que o reflow. Mas não é gratuito.

Propriedades que disparam repaint (sem reflow):

  • color, background-color, background-image
  • border-color, outline
  • box-shadow, text-shadow
  • visibility (note: display: none dispara reflow, mas visibility: hidden dispara apenas repaint)

O repaint isolado raramente é o gargalo principal. Mas quando combinado com reflows frequentes — tipo numa animação que altera background-color junto com width — o custo acumula rápido. É morte por mil cortes.

Compositing — A Via Rápida Pela GPU

Agora chegamos à parte boa.

O compositing é a operação mais barata do pipeline. Quando o browser só precisa recompor camadas já pintadas — sem recalcular layout ou repintar pixels — ele delega o trabalho à GPU numa thread separada (a compositor thread). Isto significa que a animação não compete com JavaScript pelo main thread. Zero conflito.

As propriedades que vão diretamente ao compositor, sem reflow nem repaint:

  • transform (translate, scale, rotate, skew)
  • opacity
  • filter (blur, brightness, contrast, etc.)

Em 2026, estas são as propriedades hardware-accelerated por padrão em todos os browsers modernos. As propriedades background-color e clip-path estão a caminho de se juntar à lista — mas por enquanto, não conte com isso em produção.

Esta é a razão pela qual a regra de ouro para animações continua a mesma: anime apenas transform e opacity. Tudo o resto força o browser a passar por etapas mais caras do pipeline.

Animações de Alta Performance — Código Prático

Chega de teoria. Vejamos exemplos concretos da diferença entre animar propriedades de layout e propriedades compostas:

/* ❌ Mau — animar 'left' dispara reflow a cada frame */
.slider-bad {
  position: absolute;
  left: 0;
  transition: left 0.3s ease;
}
.slider-bad.active {
  left: 200px;
}

/* ✅ Bom — animar 'transform' usa apenas compositor */
.slider-good {
  transform: translateX(0);
  transition: transform 0.3s ease;
}
.slider-good.active {
  transform: translateX(200px);
}

Para animações de entrada/saída de elementos:

/* ❌ Mau — animar height causa reflow cascata */
.dropdown-bad {
  height: 0;
  overflow: hidden;
  transition: height 0.3s ease;
}
.dropdown-bad.open {
  height: 300px;
}

/* ✅ Bom — usar transform para simular a expansão */
.dropdown-good {
  transform: scaleY(0);
  transform-origin: top;
  transition: transform 0.3s ease;
}
.dropdown-good.open {
  transform: scaleY(1);
}

O segundo exemplo pode exigir ajustes visuais (como um clip-path para evitar que o conteúdo interno pareça comprimido), mas o ganho de performance é significativo. Em dispositivos mobile com CPUs mais modestas, a diferença é noite e dia.

will-change — O Turbo Que Pode Explodir o Motor

A propriedade will-change avisa o browser antecipadamente sobre que propriedades vão mudar, permitindo-lhe preparar otimizações — como promover o elemento a uma camada GPU dedicada.

/* Informar o browser que transform vai mudar */
.card-animada {
  will-change: transform;
}

/* Aplicar via JS antes da animação, remover depois */
element.style.willChange = 'transform';
element.addEventListener('transitionend', () => {
  element.style.willChange = 'auto';
});

Mas cuidado — will-change é uma dica de otimização, não uma varinha mágica. Usá-lo indiscriminadamente tem consequências bem reais:

  • Consumo de memória: Cada camada GPU consome memória. Um carrossel com 10 imagens de 800×600px e will-change: transform em cada uma requer aproximadamente 19 MB adicionais de memória só para esse componente. Num telemóvel mid-range, isso pesa.
  • Sobre-otimização: O browser já faz otimizações internas. Adicionar will-change pode interferir com essas heurísticas, resultando em performance pior do que sem ele
  • Limite de camadas: Criar demasiadas camadas GPU pode saturar a VRAM, especialmente em dispositivos mobile

Regra prática: Use will-change como último recurso — apenas quando mediu e confirmou que a animação específica tem problemas de performance. Aplique-o antes da animação iniciar e remova-o após terminar. Nunca o coloque globalmente em CSS estático sem motivo concreto.

CSS Containment: Isolando o Impacto com contain

Então, como limitar o estrago quando um reflow acontece?

A propriedade contain do CSS permite dizer ao browser que o conteúdo interno de um elemento é independente do resto da página. Quando algo muda dentro de um elemento contido, o browser só faz reflow naquela sub-árvore, em vez de recalcular o documento inteiro. É como criar paredes corta-fogo no seu layout.

/* Valores individuais */
.card {
  contain: layout;  /* Isola cálculos de layout */
}

.widget {
  contain: paint;   /* Garante que nada pinta fora dos limites */
}

/* Shorthand mais comum */
.section-independente {
  contain: content; /* Equivale a layout + paint + style */
}

/* Máximo isolamento */
.componente-isolado {
  contain: strict;  /* layout + paint + style + size */
}

Os valores mais úteis na prática:

  • contain: layout — o reflow não se propaga para fora do elemento. Ideal para componentes que mudam frequentemente (listas, feeds, widgets)
  • contain: content — combina layout, paint e style containment. O valor mais equilibrado para a maioria dos casos
  • contain: strict — o isolamento máximo, mas exige que o elemento tenha dimensões explícitas (width/height), caso contrário será renderizado com tamanho zero. Já perdi uns bons minutos a debugar isso.

Na prática, aplique contain: content a secções independentes da sua página — cartões de produto, widgets laterais, blocos de comentários — qualquer componente cujo layout interno não afeta o resto da página.

content-visibility: auto — Renderização Lazy para o DOM

Se contain isola o impacto de mudanças, content-visibility: auto vai um passo além: diz ao browser para nem sequer renderizar elementos fora do viewport. O browser salta completamente layout, paint e cálculo de estilos para esses elementos até que estejam prestes a entrar em vista.

É basicamente lazy loading para renderização do DOM inteiro — não apenas imagens.

.secao-abaixo-da-dobra {
  content-visibility: auto;
  contain-intrinsic-size: auto 500px;
}

O contain-intrinsic-size é crucial aqui: como o browser não renderiza o conteúdo, precisa de uma estimativa do tamanho para calcular corretamente a barra de scroll. O valor auto 500px diz "use 500px como estimativa inicial, mas depois de renderizar uma vez, lembre-se do tamanho real". Sem isso, a scrollbar vai ficar aos saltos — e os utilizadores percebem.

Resultados reais

Os ganhos são francamente impressionantes:

  • No demo do web.dev, content-visibility: auto proporcionou um boost de 7× na performance de renderização no carregamento inicial
  • Em testes de campo, páginas com content-visibility renderizaram em aproximadamente ¼ do tempo
  • A propriedade tornou-se Baseline Newly Available em setembro de 2025, com suporte completo em Chrome, Firefox e Safari

Quando usar (e quando não usar)

<!-- ✅ Ideal para secções longas abaixo da dobra -->
<section class="comentarios" style="content-visibility: auto; contain-intrinsic-size: auto 800px;">
  <!-- centenas de comentários -->
</section>

<!-- ✅ Ideal para artigos longos com muitas secções -->
<article>
  <section class="intro">...</section>
  <section class="corpo" style="content-visibility: auto; contain-intrinsic-size: auto 1200px;">...</section>
  <section class="relacionados" style="content-visibility: auto; contain-intrinsic-size: auto 600px;">...</section>
</article>

<!-- ❌ NÃO use no conteúdo acima da dobra -->
<!-- ❌ NÃO use em elementos pequenos (o overhead supera o ganho) -->

Nota importante sobre acessibilidade: conteúdo com content-visibility: auto permanece disponível na árvore de acessibilidade e é pesquisável com Ctrl+F. O browser só salta a renderização visual, não remove o conteúdo do DOM. Portanto, é seguro para SEO e leitores de ecrã — pode usá-lo sem preocupações.

Layout Thrashing — O Erro Mais Comum (e Mais Caro) em JavaScript

Este é, na minha opinião, o problema de performance de renderização mais comum em projetos reais. E o mais fácil de introduzir sem perceber.

O layout thrashing ocorre quando JavaScript alterna rapidamente entre leitura e escrita de propriedades de layout, forçando o browser a executar reflows síncronos repetidos. É como perguntar ao GPS a rota, mudar de caminho, perguntar de novo, mudar de novo — sem nunca deixar o GPS recalcular em paz.

// ❌ Layout thrashing — reflows forçados em cada iteração
const boxes = document.querySelectorAll('.box');
boxes.forEach(box => {
  const width = box.offsetWidth;       // leitura → força reflow
  box.style.width = width * 2 + 'px'; // escrita → invalida layout
  const height = box.offsetHeight;     // leitura → força reflow de novo!
  box.style.height = height * 2 + 'px';
});

// ✅ Sem thrashing — leituras primeiro, escritas depois
const boxes = document.querySelectorAll('.box');
const dimensions = Array.from(boxes).map(box => ({
  width: box.offsetWidth,
  height: box.offsetHeight
}));
boxes.forEach((box, i) => {
  box.style.width = dimensions[i].width * 2 + 'px';
  box.style.height = dimensions[i].height * 2 + 'px';
});

O padrão é simples: leia tudo primeiro, escreva tudo depois. Nunca intercale leituras e escritas de propriedades de layout. E se precisar coordenar animações baseadas em layout com JavaScript, use requestAnimationFrame para agrupar as alterações num único frame:

// ✅ requestAnimationFrame agrupa alterações no próximo frame
requestAnimationFrame(() => {
  element.style.transform = ;
  element.style.opacity = novaOpacidade;
});

Ferramentas para Diagnosticar Problemas de Renderização

Não otimize às cegas — isso é receita para perder tempo. Use ferramentas para identificar exatamente onde estão os gargalos.

Chrome DevTools — Performance Panel

A aba Performance do Chrome DevTools é a ferramenta indispensável. Grave uma sessão de interação (faça scroll, clique em botões, abra menus) e analise o flame chart. Procure por:

  • Barras roxas longas (Layout): reflows caros — clique nelas para ver que elementos foram afetados
  • Barras verdes longas (Paint): repaints extensos
  • Tarefas longas (>50ms): qualquer tarefa que ultrapasse 50ms pode causar jank visível

Chrome DevTools — Rendering Panel

Ative "Paint flashing" (Ctrl+Shift+P → "Show Rendering") para ver em tempo real que áreas do ecrã estão a ser repintadas. Áreas verdes significam repaint. Se vir a página inteira a piscar verde durante um scroll suave, tem um problema sério de paint — e provavelmente sabe exatamente o que precisa corrigir depois de ler este guia.

Ative também "Layout Shift Regions" para visualizar reflows que causam deslocamento de conteúdo — útil para encontrar a origem de problemas de CLS.

Lighthouse — Audit de Performance

O Lighthouse sinaliza "Avoid non-composited animations" quando detecta animações que forçam reflow ou repaint. Cada animação listada é uma oportunidade direta de otimização — migre a propriedade animada para transform ou opacity e o aviso desaparece.

FAQ — Perguntas Frequentes sobre Performance de Renderização

Qual a diferença entre reflow e repaint?

O reflow (layout) recalcula a posição e geometria dos elementos — é caro e cascata para elementos vizinhos. O repaint apenas redesenha pixels sem alterar posições — é mais leve. Toda alteração que causa reflow também causa repaint, mas nem todo repaint causa reflow. Propriedades como width e height causam reflow; propriedades como color e background-color causam apenas repaint.

Como saber se o meu site tem problemas de renderização?

Abra o Chrome DevTools, grave uma sessão na aba Performance, e procure por tarefas longas (>50ms) e blocos de Layout extensos. Ative "Paint flashing" na aba Rendering para visualizar repaints em tempo real. Se vir a página inteira a ser repintada durante scroll ou animações, há espaço para otimização. O Lighthouse também sinaliza animações não compostas no relatório de performance.

content-visibility: auto afeta o SEO?

Não negativamente. O conteúdo com content-visibility: auto permanece no DOM e na árvore de acessibilidade — o browser apenas salta a renderização visual até que seja necessária. Os motores de busca que renderizam JavaScript (como o Googlebot) conseguem aceder a todo o conteúdo normalmente. A propriedade é segura para SEO e pode até melhorar indiretamente o ranking ao contribuir para melhores métricas de Core Web Vitals.

Devo usar will-change em todas as animações?

Não, por favor não faça isso. O will-change cria camadas GPU adicionais que consomem memória. Use-o apenas como último recurso quando mediu e confirmou que uma animação específica tem problemas de performance. Aplique-o momentos antes da animação iniciar e remova-o depois. Na maioria dos casos, usar transform e opacity para animar já é suficiente para obter compositing pela GPU, sem necessidade de will-change.

Quais propriedades CSS são seguras para animar sem causar jank?

transform e opacity são as únicas propriedades que vão diretamente ao compositor em todos os browsers modernos, sem causar reflow ou repaint. A propriedade filter (blur, brightness, etc.) também é compositor-safe na maioria dos browsers em 2026. Evite animar width, height, top, left, margin, padding ou font-size — todas causam reflow a cada frame, e o utilizador vai sentir.

Sobre o Autor Editorial Team

Our team of expert writers and editors.