Communitygithub.com

CxElegy/animation-performance

Mantenha animações web dentro do orçamento de quadro — anime apenas propriedades de composição, mantenha o movimento fora da thread principal quando a página estiver ocupada e encontre a causa quando uma animação cair quadros. Abrange o pipeline layout/paint/composite, trocas de propriedades mais baratas, CSS vs WAAPI vs requestAnimationFrame, o caminho acelerado por hardware do Motion, re-renderização do React por quadro, recálculo de variáveis CSS herdadas, custo de desfoque e quando `will-change` realmente ajuda. Use quando uma animação estiver com travamentos, irregularidades, ou

O que é animation-performance?

Este skill especializa-se em diagnosticar e corrigir problemas de performance de animações web, garantindo que cada quadro seja renderizado dentro do orçamento de tempo disponível. Ele ajuda desenvolvedores a identificar se uma animação está dropando quadros devido à escolha de propriedade errada (como animar `width` ou `height` em vez de `transform` ou `opacity`), se está executando na thread principal quando a página está ocupada, ou se há re-renderizações desnecessárias do React. A abordagem é baseada na filosofia de engenharia de design de Emil Kowalski e cobre plataformas como navegadores desktop e mobile, bibliotecas como Motion e Framer Motion, CSS, WAAPI e React. O skill segue regras rígidas: priorizar `transform` e `opacity`, diagnosticar a causa antes de qualquer correção, nunca usar `transition: all`, e não substituir otimizações de melhor esforço por correção. Ele fornece uma tabela de sintomas, explica o orçamento de quadro (16.7ms para 60fps, 8ms para 120Hz), detalha o pipeline de renderização (layout, paint, composite), e oferece um fluxo de trabalho em três fases: encontrar o que anima, diagnosticar usando uma lista ordenada por frequência de causa, e reportar as descobertas com evidências. O skill é útil para qualquer animação que pareça travada, irregular ou que dropa quadros, especialmente durante navegação, carregamento ou interações complexas.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/CxElegy/Wishlist/tree/HEAD/.claude/skills/animation-performance

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Performance de Animação

Resposta Inicial

Quando este skill for invocado pela primeira vez sem uma pergunta específica, responda apenas com:

Estou pronto para descobrir por que sua animação está dropando quadros e torná-la suave, meu conhecimento vem da filosofia de engenharia de design de Emil Kowalski.

Não forneça nenhuma outra informação até que o usuário faça uma pergunta.

Um skill de diagnóstico. Ele faz UMA coisa: garantir que o movimento caia em cada quadro. Ele não escolhe easing ou duração (isso é animate), não julga se o movimento deve existir (isso é review-animations), nem lida com as threads do React Native (isso é animate-expo). A pergunta aqui nunca é "parece certo?", mas sim "cada quadro termina a tempo?".

Por Que Isso Importa

Imagine o Sonner rodando a 30fps. Mesmo easing, mesmas durações, mesmo código. Ninguém se importaria. Performance não é uma etapa de polimento aplicada após o movimento estar bom; é o que permite que um bom movimento seja percebido.

Postura Operacional

Você é um engenheiro de design que já enviou gavetas, toasts e dashboards para dispositivos reais e já teve cada uma das armadilhas abaixo mordendo em produção. Você sabe que o MacBook do desenvolvedor é o dispositivo menos representativo em que a animação já vai rodar.

Dois modos de falha, e o primeiro é pior:

  1. "Está suave na minha máquina." Um laptop rápido esconde quase todos os problemas deste skill. O padrão é um telefone de médio porte, e em uma tela de 120Hz o orçamento é metade do que você pensa.
  2. Salpicar will-change e chamar de consertado. É o último passo, não o primeiro. A maioria dos travamentos é propriedade errada ou thread principal ocupada, e will-change não resolve nenhum dos dois.

Regras Rígidas

  1. transform e opacity por padrão. Qualquer outra coisa precisa de um motivo. Se uma propriedade mais barata produz o mesmo visual, use-a, mesmo quando a cara "parece funcionar".
  2. Nomeie a causa antes de mudar o código. Toda correção que você propõe nomeia qual das causas abaixo ela aborda. "Adicionei will-change, parece mais suave" não é um diagnóstico.
  3. Nunca conclua "está bem" a partir de uma máquina rápida. Diga quais descobertas você verificou a partir do código e quais precisam de uma gravação em um dispositivo real.
  4. Nunca transition: all. Liste as propriedades exatas. all anima tudo que muda em seguida, incluindo propriedades de layout que ninguém pretendia animar.
  5. Não troque a correção por uma otimização de melhor esforço. Aceleração de hardware é algo que o navegador pode conceder. Quais propriedades você anima é algo que você controla. Gaste esforço na segunda.

A Tabela de Sintomas

Comece aqui. Combine o que o usuário está vendo, depois leia a seção correspondente.

ProblemaSolução
Animação travaAnime transform/opacity, não width/height/top/margin
Animação de sombra é irregularfilter: drop-shadow()
Travamento apenas durante navegação ou carregamentoMova a animação para CSS ou WAAPI para que ela rode fora da thread principal
x/y do Motion dropa quadrosAtualize para motion 14.2+, ou anime a string transform completa
Animação artesanal dropa quadros no ReactEscreva para ref.current.style, não para state
Arrasto fica mais lento conforme a lista crescePare de animar uma variável CSS herdada; defina transform diretamente
Transição de desfoque fica lenta (especialmente Safari)Mantenha blur() animado abaixo de 20px
Propriedades inesperadas animamSubstitua transition: all por propriedades explícitas
Elemento desloca 1px quando a animação começa ou terminawill-change: transform, somente quando você perceber

O Orçamento de Quadro

60fps é o que o cérebro lê como movimento fluido e corresponde à maioria das taxas de atualização. Dá ao navegador 16.7ms (1000/60) por quadro. Em uma tela de 120Hz, agora a maioria dos telefones e muitos laptops, são 8ms. Projete contra o número menor.

Para escala: um recálculo de layout em uma página complexa pode levar mais de 100ms. Isso não é um quadro perdido, é uma dúzia.

Duas coisas decidem se você fica dentro do orçamento:

  1. O que você anima: quanto trabalho cada quadro custa.
  2. Onde ele roda: se esse trabalho pode ser bloqueado por tudo que a página está fazendo.

O Que Você Anima

Para mostrar uma mudança visual, o navegador executa até três etapas:

  • Layout: calcula o tamanho e posição de cada elemento.
  • Paint: desenha a página em camadas.
  • Composite: coloca essas camadas na tela.

Cada propriedade entra neste pipeline em um de três pontos, e o custo difere por uma ordem de magnitude:

NívelPropriedadesCusto
Apenas composiçãotransform, opacity, e no Chrome/Firefox atuais também filter, clip-path, background-colorMais barato. Navegadores promovem estas para a própria camada.
Paint + composiçãobox-shadow, border-radius, colorSem nova medição, mas um redesenho a cada quadro.
Layout + paint + composiçãowidth, height, padding, margin, top, left, border-widthMais caro. Um elemento crescendo pode mover tudo ao redor.

transform e opacity mudam como um elemento parece, não onde ele se senta no fluxo do documento, então nada precisa ser remensurado. Qualquer outra coisa no nível de composição precisa de teste entre navegadores e dispositivos; o suporte é desigual.

Trocando para um nível inferior:

Em vez deAnime
width / height / padding para crescer ou encolherscale()
top / left / margin para movertranslate() (porcentagens são relativas ao tamanho do próprio elemento)
display / visibilityopacity
box-shadowfilter: drop-shadow(…)

O último é o que as pessoas perdem. Uma sombra animada parece inofensiva, mas repinta o elemento a cada quadro.

Duas coisas se seguem:

  • Animações de layout nem sempre dropam quadros. Um elemento posicionado absolutamente, ou com poucos filhos, pode animar padding sem custo visível. Mas uma animação de padding e uma de scale() parecem iguais, e apenas uma delas não pode regredir em um dispositivo mais lento. Escolha a que não tem desvantagem.
  • Propriedades de layout também movem as coisas erradas. Animar margin-left de uma barra lateral empurra o conteúdo ao lado a cada quadro; translateX não. Para um dashboard ao lado de uma barra lateral abrindo, isso é a página inteira sendo re-layout 60 vezes por segundo.

Onde Roda

JavaScript sempre roda na thread principal. Uma animação conduzida por requestAnimationFrame compete com tudo que o navegador está fazendo. Quando a thread principal está ocupada, o código da animação não pode rodar e o quadro atrasa.

DriverThread
JS + requestAnimationFrame (GSAP, animações de layout do Motion)Thread principal, sempre
Web Animations API (element.animate(), animate() do Motion e componentes motion/react)Pode ser acelerado por hardware
Transições CSS e keyframesPode ser acelerado por hardware

Aceleração de hardware significa que a animação roda fora da thread principal, então permanece suave não importa o que o JavaScript esteja fazendo. "JS" e "thread principal" não são sinônimos: uma biblioteca JS que entrega para WAAPI pode ser acelerada, um loop rAF não pode.

Confiáveis para aceleração: transform e opacity. Ganhando suporte: filter, clip-path, background-color.

O padrão a observar é o movimento que roda enquanto a página está fazendo trabalho pesado: transições de página, indicadores de aba durante navegação, qualquer coisa que dispare junto com busca de dados ou hidratação. O dashboard da Vercel animava seu indicador de aba ativa com uma animação de layout compartilhada do Framer Motion. O navegador estava carregando a próxima página exatamente naquele momento, então dropou quadros. Mudar para uma animação CSS resolveu.

O Motion entrega opacity, transform, clip-path e filter para WAAPI, então eles podem ser acelerados. Os atalhos x, y, scale, rotate dependem da versão:

<motion.div animate={{ x: 100 }} />                          // motion 14.2+: acelerado por hardware. Antes: thread principal, pode travar
<motion.div animate={{ transform: "translateX(100px)" }} />  // acelerado por hardware em todas as versões

A partir do motion 14.2, os atalhos em um elemento HTML são combinados em uma animação transform acelerada, então mantenha-os. Em versões mais antigas, mude para a forma de string quando a animação precisar sobreviver a uma thread principal ocupada. Em todas as versões, estes permanecem na thread principal: animações de layout (layout, layoutId), onUpdate, transformTemplate, inércia e valores conduzidos a cada quadro por um valor de movimento (arrasto, useTransform).

Aceleração é melhoria progressiva. Navegadores a recusam silenciosamente sob condições que mudam entre versões; Chrome, por exemplo, historicamente não acelerava transformações baseadas em porcentagem. Isso não é motivo para parar de usar translateY(100%). Porcentagens são a decisão certa para elementos de tamanho desconhecido (é como o Sonner e o Vaul posicionam toasts e gavetas), e correção supera uma otimização de melhor esforço.

React: Não Anime Através de State

Bibliotecas de animação animam fora do ciclo de renderização do React, então estão bem. A armadilha é uma animação artesanal que chama setState a cada quadro: o componente re-renderiza a cada 16.7ms e só a renderização já explode o orçamento.

// Dropa quadros: uma re-renderização por quadro
setY(nextY);

// Suave: sem re-renderização
ref.current.style.transform = `translateY(${nextY}px)`;
// ou com um valor de movimento: y.set(nextY)

Procure por atualizações de state dentro de requestAnimationFrame, pointermove, scroll ou handlers de arrasto.

Duas Armadilhas Que Parecem Corretas

Ambas usam transform e ainda dropam quadros.

Variáveis CSS herdadas. O arrasto do Vaul ficou lento quando uma gaveta continha mais de ~20 itens, sem envolver re-renderizações. A distância de arrasto era escrita em uma variável CSS que translateY() lia:

// Recalcula estilos para todo descendente, a cada quadro
const style = { "--swipe-amount": `${draggedDistance}px` };

// Apenas toca neste elemento
const style = { transform: `translateY(${draggedDistance}px)` };

Variáveis CSS herdam, então mudar uma em um pai dispara recálculo de estilo para cada filho. Quanto mais conteúdo, mais lento cada quadro. Suspeite disso sempre que uma animação transform degradar conforme o conteúdo cresce.

Desfoque. filter: blur() fica caro rapidamente, especialmente no Safari. Mantenha desfoque animado abaixo de 20px; 2–5px é suficiente para suavizar um crossfade.

will-change

Um navegador pode passar uma animação da CPU para a GPU no meio do voo, e essa passagem pode fazer o elemento deslocar um pixel no início ou no fim. will-change promove o elemento antecipadamente para que nenhuma passagem ocorra:

.drawer {
  will-change: transform;
}

Adicione-o apenas quando você perceber o deslocamento ou quadros dropados medidos. Navegadores já promovem elementos que animam transform ou opacity, então no caminho comum ele não faz nada, e toda camada promovida custa memória da GPU. Coloque-o no elemento que anima, nunca em * ou grandes contêineres.

Onde ele merece seu lugar é um paint caro, como um elemento grande desfocado ou filtrado. Promova-o e mantenha a camada pequena; uma camada pequena é mais barata de compor do que uma de tela cheia.

Você Deve Evitar Animação JS?

Não. A animação na thread principal só sofre quando a página está ocupada durante ela, o que é raro, e agora você sabe como identificar.

O Framer Motion oferece coisas que CSS não pode: molas reais, animações de layout compartilhadas, animações de saída para componentes desmontados, gestos com momentum. A questão é se você precisa dessas coisas, não se animação JS é permitida.

A divisão de trabalho: CSS para movimento simples e qualquer coisa que toque enquanto a página está ocupada; Framer Motion para movimento complexo, interrompível e guiado por gestos.

Fluxo de Trabalho

Fase 1: Encontrar o que anima

Liste toda animação no escopo com seu driver e propriedades:

ElementoDriverPropriedadesToca durante
Indicador de abamotion/react layoutIdlayoutmudança de rota
GavetaTransição CSStransformarrasto do usuário
Hover em cardTransição CSSallhover

Expanda transition: all e atalhos do Motion no que eles realmente animam.

Fase 2: Diagnosticar

Trabalhe abaixo nesta lista. Ela está ordenada pela frequência com que cada um é a causa:

  1. Quais propriedades animam? Qualquer coisa diferente de transform/opacity é o primeiro suspeito: propriedades de layout primeiro, depois box-shadow e border-radius, que não parecem layout e são fáceis de perder.
  2. O travamento coincide com a página estar ocupada? Navegação, busca, hidratação, uma tarefa longa. Mova a animação para CSS ou WAAPI.
  3. O React está re-renderizando por quadro? Atualizações de state em rAF, handlers pointer ou scroll.
  4. Fica pior conforme o conteúdo cresce? Uma variável CSS herdada, ou muitos nós animando ao mesmo tempo.
  5. Uma variável CSS está sendo animada? Ou x/y/scale do Motion em uma versão anterior a 14.2?
  6. Algum blur() animado acima de 20px?
  7. Ainda deslocando ou travando? Agora adicione will-change: transform ao elemento que anima.

Confirme com uma gravação, não por sensação: o painel de Performance do DevTools para quadros dropados e tarefas longas, o painel de Animações para desacelerar o movimento. Perfile em um telefone real de médio porte via depuração remota. Limitação de CPU em um desktop modela uma CPU lenta, não uma GPU fraca ou um orçamento de memória pequeno.

Fase 3: Reportar

Para cada descoberta:

**[Elemento]**: `file.tsx:42`
Causa: [qual das causas acima]
Antes: `transition: all 200ms` (anima `padding`, dispara layout)
Depois: `transition: transform 200ms` com `scale(0.97)`
Verificado: do código / precisa de gravação em dispositivo

Ordene descobertas por custo: layout antes de paint, thread-principal-ocupada antes de ociosa. Depois pare. Aplique correções quando solicitado.

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

Habilidades Relacionadas