- Fundamentos: O que é Performance e por que Importa
- Core Web Vitals: LCP, INP e CLS na Prática
- Entrega & Rendering: CDN, Lazy Loading e Estratégias de Render
- JavaScript: Seu Bundle Está Pesado Demais
- CSS: Critical Path e Layout Thrashing
- DevTools: Explorando a Performance no Navegador
- Monitoramento: Medindo o que Importa em Produção
CSS parece inocente comparado ao JavaScript, mas tem seu próprio conjunto de armadilhas de performance. Um arquivo CSS bloqueando a renderização, uma propriedade animada errada ou leituras e escritas DOM alternadas podem custar centenas de milissegundos.
O Pipeline de Renderização
Para entender CSS performance, é preciso entender como o browser renderiza uma página — cada etapa tem seu custo e suas armadilhas.
Constrói o DOM e o CSSOM em paralelo. CSS é render-blocking — o browser não renderiza nada até ter o CSSOM completo.
DOM + CSSOM combinados. Apenas elementos visíveis com seus estilos computados. display: none é excluído.
Calcula posição e tamanho de cada elemento. Qualquer mudança em propriedades geométricas (width, height, margin) dispara esta fase — caro.
Preenche pixels em layers. Mudanças em background-color, color, border disparam paint — menos caro que layout.
Combina layers e envia ao display. transform e opacity só chegam aqui — sem layout nem paint. São as propriedades de animação ideais.
Qualquer mudança que afeta geometria força o browser a refazer Layout → Paint → Composite para os elementos afetados e seus descendentes.
CSS bloqueia não só a renderização — também bloqueia a execução de JavaScript síncrono. O browser não executa scripts antes de ter o CSSOM, porque o script poderia consultar estilos computados. CSS lento atrasa JS, que atrasa renderização em cascata.
Critical CSS
Critical CSS é o subconjunto mínimo de estilos necessários para renderizar o conteúdo above the fold sem arquivos CSS externos.
<head>
<!-- ✅ Critical CSS inline — sem download, sem bloqueio -->
<style>
body { margin: 0; font-family: Inter, sans-serif; }
.hero { display: flex; min-height: 100vh; align-items: center; }
.hero-title { font-size: 3rem; font-weight: 700; }
</style>
<!-- ✅ CSS não-crítico carregado de forma assíncrona -->
<link
rel="stylesheet"
href="/styles.css"
media="print"
onload="this.media='all'"
/>
<noscript><link rel="stylesheet" href="/styles.css" /></noscript>
</head>O truque do media="print": o browser baixa o arquivo mas não o trata como render-blocking. Quando carrega, o onload muda media para all, ativando os estilos sem bloqueio.
critters — usado internamente pelo Next.js e Angular para extrair critical CSS automaticamente.
critical (CLI) — extrai critical CSS de qualquer página. Útil para projetos sem framework.
PurgeCSS — remove CSS não utilizado antes de extrair o critical.
Next.js extrai e inline Critical CSS automaticamente em produção. Se você usa Next.js, isso já está resolvido — não precisa configurar nada extra.
Layout Thrashing
Layout thrashing acontece quando o browser é forçado a recalcular o layout repetidamente porque o código alterna entre ler e escrever propriedades do DOM.
Propriedades que forçam reflow ao serem lidas
- Propriedades de offset
element.offsetWidth,offsetHeight,offsetTop,offsetLeft— lidas após uma escrita, forçam reflow imediato - getBoundingClientRect()
Retorna a posição e tamanho do elemento na viewport. Força layout se o DOM estiver "sujo".
- scrollTop / scrollLeft
Ler posição de scroll após uma escrita DOM pode forçar layout.
- getComputedStyle()
Consulta estilos calculados — requer que o CSSOM esteja atualizado, forçando recálculo se necessário.
A solução: batch reads antes de writes
// ❌ Layout thrashing — lê e escreve alternando por elemento
elements.forEach(el => {
const h = el.offsetHeight; // read → força reflow
el.style.height = h + 1 + 'px'; // write → invalida layout
});
// ✅ Batch: lê tudo primeiro, depois escreve tudo
const heights = Array.from(elements).map(el => el.offsetHeight); // batch reads
elements.forEach((el, i) => {
el.style.height = heights[i] + 1 + 'px'; // batch writes
});Animações Performáticas
width, height, margin, padding, top, left — disparam Layout + Paint + Composite. Nunca anime essas propriedades.
background-color, color, border-color, box-shadow — disparam Paint + Composite. Evite em animações a 60fps.
transform e opacity — processados pelo compositor thread, separado do main thread. Rodam a 60fps mesmo com JS bloqueando.
/* ❌ Causa layout em cada frame — jank em dispositivos lentos */
@keyframes slideIn {
from { left: -100px; }
to { left: 0; }
}
/* ✅ Só compositor — 60fps garantido */
@keyframes slideIn {
from { transform: translateX(-100px); }
to { transform: translateX(0); }
}will-change
/* Promove elemento para compositor layer antecipadamente */
.animated-element {
will-change: transform, opacity;
}will-change diz ao browser para criar uma compositor layer antecipadamente, eliminando o custo de criá-la no momento da animação.
Cada compositor layer consome memória de GPU. Não aplique will-change em todos os elementos — só nos que realmente vão animar. E remova após a animação: element.style.willChange = 'auto'.
Técnicas Avançadas
/* Cartão isolado — mudanças internas não afetam o layout externo */
.card {
contain: layout style paint;
/* ou o shorthand: */
contain: content;
}contain diz ao browser que um elemento é independente do restante do documento. Atualizar um cartão numa lista não re-triggeriza layout do documento inteiro.
/* Pula renderização de itens fora da viewport */
.article-item {
content-visibility: auto;
contain-intrinsic-size: 0 300px; /* estimativa de altura para scroll bar */
}content-visibility: auto é virtualização nativa em CSS — sem JavaScript. Pode reduzir tempo de renderização inicial em 50-70% em páginas longas.
Checklist de CSS Performance
- CSS crítico inline no HTML
Estilos necessários para o conteúdo above the fold devem estar inline — zero arquivos externos bloqueando o primeiro paint.
- Anime só transform e opacity
Qualquer outra propriedade causa reflow ou repaint, destruindo a fluidez em dispositivos móveis.
- Evite layout thrashing
Batch reads antes de writes. Se precisar alternar, use requestAnimationFrame para separar as fases.
- Use will-change com moderação
Só em elementos que vão animar, removendo após a animação. Cada layer consome memória de GPU.
- Considere CSS Containment
contain: contentem componentes isolados (cards, itens de lista) evita reflows globais desnecessários. - content-visibility para listas longas
content-visibility: autopula renderização de itens fora da viewport sem JavaScript.
No próximo artigo, exploramos o Chrome DevTools em detalhe — Performance panel, flame graph, Memory, Coverage e Lighthouse integrado.
As minhas notas
Selecione um trecho e use Destacar no menu (somente neste aparelho).