Back

Depurando Animações CSS Instáveis com o DevTools

Depurando Animações CSS Instáveis com o DevTools

Animações CSS instáveis resultam de orçamentos de quadros não cumpridos: a 60fps, o navegador tem aproximadamente 16,7ms para produzir cada quadro, e qualquer quadro que demore mais — por causa de recálculo de layout, pintura ou um thread principal sobrecarregado — reduz a taxa de quadros e se manifesta como uma trepidação visível. A solução raramente é “adicionar mais will-change.” É um diagnóstico: identificar qual estágio do pipeline de renderização demorou mais, e em qual thread. Este artigo apresenta um fluxo de trabalho sistemático de quatro painéis no Chrome DevTools atual — Rendering, Performance, Animations e Layers — para rastrear uma animação instável até sua causa, com um exemplo prático de antes/depois que você pode reproduzir.

O leitor-alvo conhece que transform e opacity são operações leves, mas não possui um procedimento definido. A animação que funcionava bem no laptop e travava em um Android intermediário é o caso canônico. Você precisa de triagem, não de mais uma dica sobre propriedades.

Principais Conclusões

  • A 60fps, o navegador tem cerca de 16,7ms por quadro para concluir estilo, layout, pintura e composição; perder esse orçamento uma única vez produz uma trepidação visível (documentação de performance do Chrome DevTools).
  • Diagnostique na ordem dos painéis: Rendering para uma verificação visual rápida, Performance para identificar a causa raiz do quadro lento, Animations para isolar o keyframe problemático, Layers para auditar o custo de memória do compositor.
  • Barras roxas de Recalculate Style ou Layout diretamente abaixo de uma barra amarela de JavaScript na faixa Main do painel Performance indicam um layout síncrono forçado; o triângulo vermelho leva diretamente à linha exata do JS.
  • O compositor pode animar transform e opacity sem layout ou pintura; animar left/top/width/height força um layout no thread principal em cada quadro (CSS Triggers).
  • Animações orientadas por rolagem que utilizam animation-timeline são executadas no compositor para transform/opacity, portanto, a instabilidade delas aparece na faixa Frames, não como uma tarefa longa no thread principal.

O que é jank em animações?

Jank é um quadro descartado ou atrasado que o usuário consegue perceber. Para manter 60fps, o navegador deve concluir cada quadro em aproximadamente 16,7ms (1000ms ÷ 60). Essa janela abrange recálculo de estilo, layout, pintura e composição para aquele quadro. Quando um único quadro demora mais que o esperado, o navegador perde seu prazo, a taxa de quadros efetiva cai — para 30fps ou menos — e o movimento parece pular. O guia de performance de renderização do Google enquadra esse mesmo orçamento: mudanças visuais fluidas precisam caber dentro da janela por quadro, e animações são o lugar mais visível onde o orçamento é ultrapassado, porque o olho está acompanhando um movimento contínuo.

O motivo pelo qual um quadro lento é perceptível enquanto uma requisição de rede lenta não é: a animação é uma sequência de quadros que o cérebro integra como movimento. Um único quadro atrasado quebra essa integração, e uma pausa no meio do movimento é percebida como um salto. É por isso que “está basicamente fluido” não é suficiente — o pior quadro, não a média, determina a qualidade percebida.

O pipeline de renderização, brevemente

Cada atualização visual percorre um pipeline fixo: parse → style → layout → paint → composite. O navegador analisa HTML e CSS transformando-os em DOM e CSSOM, calcula quais estilos se aplicam (style), determina a geometria e posição de cada caixa (layout), rasteriza pixels em camadas (paint) e, por fim, combina as camadas na imagem exibida (composite). O explicador do pipeline de renderização do web.dev é a referência canônica detalhada; a versão resumida é tudo que você precisa aqui.

O ponto que fundamenta o restante deste artigo: cada estágio do pipeline é executado em um thread específico e aparece em um painel específico do DevTools. Layout e paint são executados no thread principal, junto com o seu JavaScript. A composição é executada separadamente. Uma animação que apenas compõe — movendo uma camada existente, alterando sua opacidade — contorna o thread principal quase completamente. Uma animação que aciona layout arrasta trabalho de volta para o thread principal em cada quadro, onde compete com tudo o mais. Essa distinção é o que os painéis abaixo permitem visualizar.

Quais painéis do DevTools diagnosticam jank em animações?

Um diagnóstico sistemático de jank percorre quatro painéis em sequência: o painel Rendering para uma verificação visual rápida (a pintura está ocorrendo onde não deveria?), o painel Performance para gravar e identificar a causa raiz do quadro lento, o painel Animations para isolar qual keyframe ou propriedade está causando o problema, e o painel Layers para auditar se a promoção de camada do compositor está ajudando ou criando pressão de memória. Utilize-os nessa ordem: o Rendering descarta ou confirma categorias inteiras de problemas em segundos, o Performance fornece o rastreamento, o Animations estreita o escopo até a propriedade, e o Layers verifica o custo da sua correção.

Painel Rendering: a verificação rápida

O painel Rendering é sua primeira parada porque responde, de forma visual e imediata, se sua animação está repintando quando não deveria. Abra-o pelo Command Menu (Cmd/Ctrl+Shift+P, digite “Show Rendering”) ou em More Tools → Rendering (referência de renderização do Chrome DevTools). Três opções são relevantes:

  • Frame Rendering Stats exibe uma leitura ao vivo de FPS e uma sobreposição de memória GPU enquanto a animação é executada. Um número que cai bem abaixo de 60 durante a animação confirma a existência de jank.
  • Paint flashing destaca as regiões que o navegador repinta, exibindo-as em verde. Um elemento que anima apenas transform não deve produzir nenhum flash verde enquanto se move; um flash verde acompanhando a animação significa que você está acionando a pintura.
  • Layer borders contorna as camadas do compositor em laranja. Use-o para confirmar que um elemento que você espera estar acelerado por hardware realmente obteve sua própria camada — e para identificar camadas que você não pretendia criar.

Passos:

  1. Abra o painel Rendering.
  2. Ative o Paint flashing e acione a animação.
  3. Se o elemento animado piscar em verde enquanto se move, a animação está pintando em cada quadro — uma propriedade de layout ou pintura está sendo animada. Isso confirma um problema no nível da propriedade e indica que você deve abrir o painel Performance em seguida.
  4. Se não houver flash, mas o movimento ainda travar, o gargalo provavelmente é JavaScript no thread principal, não pintura — também uma questão para o painel Performance.

Painel Performance: identificando a causa raiz do quadro lento

O painel Performance é onde você grava a animação e lê exatamente qual estágio do pipeline ultrapassou o orçamento do quadro. Ele exibe o gráfico de FPS, o tempo por quadro na faixa Frames, a atividade do thread Main e — no Chrome atual — uma barra lateral de Insights que sinaliza automaticamente problemas como reflow forçado (referência do Chrome DevTools performance).

Antes de gravar, limite a CPU para aproximar o dispositivo onde o jank realmente aparece. O Chrome DevTools documenta os presets de limitação de CPU em Capture settings; o painel oferece um preset de “4x slowdown”, sendo a abordagem recomendada testar com uma limitação que se aproxime de hardware menos potente (referência de performance, limitação de CPU). A limitação é importante porque o motivo mais comum para uma animação CSS passar no perfil local, mas travar em produção, é o contexto do dispositivo: um Android intermediário executando o Chrome com várias abas abertas tem uma fração do orçamento de CPU de um laptop de desenvolvimento, que a limitação aproxima, mas não pode replicar completamente sem também simular pressão de memória e carga de GPU concorrente.

Passos:

  1. Abra o painel Performance e marque Screenshots.
  2. Em Capture settings (ícone de engrenagem), defina a limitação de CPU para um preset de redução de velocidade.
  3. Clique em Record, execute a animação por alguns segundos e depois em Stop.
  4. Leia primeiro a faixa FPS/Frames. Marcas vermelhas acima dos quadros sinalizam quadros que ultrapassaram o orçamento.
  5. Amplie um quadro problemático e examine a faixa Main.

Esta é a heurística mais útil na depuração de animações:

Barras roxas abaixo de amarelo na faixa Main = layout síncrono forçado. O triângulo vermelho é o link direto para a correção.

Na faixa do thread Main, barras roxas de Recalculate Style ou Layout aparecendo diretamente abaixo de uma barra amarela de JavaScript indicam um layout síncrono forçado — o navegador foi obrigado a resolver a geometria no meio do script porque o JavaScript leu uma propriedade de layout imediatamente após escrever no DOM. Ler offsetWidth, offsetTop ou chamar getBoundingClientRect() após uma escrita de estilo força o navegador a descarregar o layout de forma síncrona; a lista canônica de Paul Irish sobre o que força layout/reflow enumera esses gatilhos. O triângulo vermelho na barra roxa abre uma entrada de Resumo com um aviso “Layout Forced” e um link para o arquivo-fonte com a linha exata do JS. O guia de layout thrashing do web.dev aborda o padrão de leitura após escrita em profundidade.

Quando não há roxo abaixo do amarelo, o JavaScript concluiu seu trabalho e permitiu que o navegador realizasse a renderização em seu próprio cronograma. Esse é o rastreamento que você está buscando.

Painel Animations: isolando o keyframe

O painel Animations permite inspecionar, percorrer e desacelerar animações ativas para que você possa identificar o jank em um keyframe ou propriedade específica, em vez de na animação como um todo. Abra-o em More Tools → Animations (documentação de animações do Chrome DevTools). O Chrome monitora as animações e as lista conforme são acionadas, permitindo inspecionar a animação capturada, percorrer sua linha do tempo e examinar seus keyframes.

Seu poder de diagnóstico vem da combinação com o Paint flashing. Desacelerar uma animação para 10% da velocidade de reprodução enquanto observa o Paint flashing no painel Rendering é a forma mais rápida de identificar qual keyframe específico aciona uma repintura — o flash verde aparece exatamente no momento em que o valor da propriedade problemática entra em vigor.

Passos:

  1. Abra o painel Animations e acione a animação para que ela apareça na lista.
  2. Defina a velocidade de reprodução para 10% (os controles estão no topo do painel).
  3. Com o Paint flashing ativado, percorra a linha do tempo e observe o flash verde.
  4. Se o flash verde aparecer em um ponto específico da linha do tempo, concentre sua investigação no keyframe ativo naquele momento.

Firefox e outros navegadores possuem seus próprios inspetores de animação; o Chrome é o pressuposto de trabalho aqui.

Painel Layers: auditando o custo do compositor

O painel Layers mostra quais elementos foram promovidos para sua própria camada do compositor, por quê, e a que custo de memória — que é como você evita adicionar will-change indiscriminadamente. Abra-o em More Tools → Layers (documentação de camadas do Chrome DevTools). Selecionar uma camada revela seu consumo de memória e o motivo da composição no painel de detalhes.

A promoção é uma troca. Mover um elemento para sua própria camada permite que o compositor o anime sem repintar os elementos vizinhos, mas cada camada aloca memória GPU para sua textura. A documentação do will-change no MDN é explícita de que a propriedade é um último recurso: aplicá-la a muitos elementos desperdiça recursos porque o navegador já otimiza as propriedades baratas por conta própria, e a promoção excessiva pode degradar a performance. Use o painel Layers para contar as camadas promovidas e verificar que cada uma justifica seu custo de memória.

Exemplo prático antes/depois: animando left vs. transform

Animar left aciona layout em cada quadro; animar transform: translateX() não aciona nem layout nem pintura. O mesmo movimento é executado em um thread diferente. Aqui está a versão problemática, animando left:

/* Quebrado: anima uma propriedade de layout */
.box {
  position: absolute;
  left: 0;
  width: 100px;
  height: 100px;
  background: tomato;
  animation: slide 1s ease-in-out infinite alternate;
}

@keyframes slide {
  to {
    left: 200px;
  }
}

O que cada painel exibe com esta versão: o painel Rendering pisca o elemento em verde durante toda a animação, porque alterar left força o layout, e o layout é sempre seguido de pintura. A faixa Main do painel Performance se preenche com barras roxas de Recalculate Style e Layout em cada quadro, e a faixa Frames mostra quadros acima do orçamento assim que você ativa a limitação de CPU. left, top, width e height acionam layout — consulte o CSS Triggers para o detalhamento por propriedade — e o layout é executado no thread principal, competindo com tudo o mais pelo orçamento de 16,7ms.

A reescrita expressa o mesmo movimento apenas com transform:

/* Corrigido: anima uma propriedade somente de composição */
.box {
  position: absolute;
  left: 0;
  width: 100px;
  height: 100px;
  background: tomato;
  animation: slide 1s ease-in-out infinite alternate;
}

@keyframes slide {
  to {
    transform: translateX(200px);
  }
}

translateX reproduz a mudança posicional via transform. Após a reescrita: o Paint flashing não exibe verde durante o movimento, a faixa Main do painel Performance não se preenche mais com roxo em cada quadro, e a animação é executada no compositor. O compositor pode animar transform e opacity sem acionar layout ou pintura, portanto o navegador move uma textura de camada existente em vez de recalcular a geometria em cada quadro.

A lista de correções

A correção é uma troca de propriedade: substitua qualquer coisa que acione layout ou pintura por transform ou opacity. A tabela mapeia cada intenção de animação para seu equivalente somente de composição.

IntençãoEvitar (força layout/pintura)Usar (somente composição)
Moverleft, top, margintransform: translate()
Redimensionarwidth, heighttransform: scale()
Rotacionarhacks que afetam layouttransform: rotate()
Esmaeceralternâncias de visibility, mudanças de backgroundopacity

As propriedades CSS mais seguras e amplamente suportadas para animar são transform (translação, escala, rotação, inclinação) e opacity, porque os navegadores geralmente podem executá-las no compositor sem acionar layout ou pintura. filter também pode ser acelerado por GPU para funções como blur(), mas o suporte e o comportamento variam, portanto verifique no painel Rendering com Paint flashing antes de assumir que é gratuito — a documentação do filter no MDN descreve a propriedade, e o CSS Triggers registra seu impacto de renderização por engine. Muitas outras propriedades animadas acionam pintura, e propriedades que alteram tamanho ou posição normalmente acionam recálculo de layout no thread principal.

Para animações controladas por JavaScript, agrupe todas as leituras do DOM antes de todas as escritas. O layout síncrono forçado no exemplo rastreado vem de ler uma propriedade de layout após uma escrita; agrupar as leituras primeiro permite que o navegador as sirva a partir do layout do quadro anterior, em vez de descarregar um novo. O guia de layout thrashing detalha o padrão.

Use will-change de forma estratégica, não por padrão. Aplique-o a um elemento que você está prestes a animar e remova-o quando a animação terminar; segundo o MDN, aplicá-lo amplamente desperdiça memória GPU porque o navegador já otimiza as propriedades baratas. Confirme o efeito no painel Layers.

Animações orientadas por rolagem: uma assinatura de jank diferente

Animações orientadas por rolagem declaradas com animation-timeline: scroll() ou animation-timeline: view() mudam onde você deve olhar no DevTools. Quando animam apenas transform ou opacity, são executadas no compositor, portanto, o jank delas não aparece como uma tarefa longa na faixa do thread Main — procure em vez disso por quadros descartados na faixa Frames. A documentação do animation-timeline no MDN e o guia de animações orientadas por rolagem do Chrome cobrem o recurso e sua linha de base de suporte nos navegadores. Se você aplicar a heurística da faixa Main e não encontrar nada, mas a faixa Frames ainda mostrar quadros acima do orçamento, suspeite que uma propriedade não-compositável tenha entrado nos keyframes orientados por rolagem.

Por que uma animação trava apenas em produção?

O DevTools cria perfis em condições controladas; a variável que ele não consegue reproduzir completamente é o contexto real do usuário — nível de CPU do dispositivo, pressão de memória, atividade concorrente. Quando um relato de jank não se reproduz localmente, esse contexto ausente geralmente é a causa. A reprodução de sessão captura isso, para que você saiba quais condições simular antes de gravar.

Execute os quatro painéis em ordem — Rendering para confirmar, Performance para identificar a causa raiz, Animations para isolar, Layers para auditar — e a próxima animação instável deixa de ser uma suposição.

Perguntas Frequentes

Contexto do dispositivo, não o código. Um Android intermediário com várias abas abertas tem uma fração do orçamento de CPU de um laptop. Ative a limitação de CPU nas Capture settings do painel Performance e use a reprodução de sessão para capturar as condições reais quando o jank em produção ocorreu.

`transform` é executado no thread do compositor sem acionar layout ou pintura — o navegador apenas move uma textura de camada existente em cada quadro. `left` ou `top` força um recálculo de layout no thread principal em cada quadro, seguido de uma pintura, competindo com o JavaScript pelo orçamento de 16,7ms.

Não de forma confiável. Apenas `transform` e `opacity` têm garantia de serem somente de composição. `filter` pode ser acelerado por GPU para funções como `blur()` em alguns engines, mas o suporte varia. Verifique no painel Rendering com Paint flashing: um flash verde significa que está pintando em cada quadro.

`animation-timeline: scroll()` e `view()` são executados no compositor quando animam apenas `transform` ou `opacity`, não produzindo nenhuma tarefa longa no thread Main. O jank aparece na faixa Frames. Se o Main não mostrar nada, mas o Frames mostrar quadros acima do orçamento, provavelmente uma propriedade não-compositável entrou nos keyframes.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay