Pular para o conteúdo
Tempo limitadoO Starter agora custa US$ 9 por mês em vez de US$ 20.Ver planos
charmotion

Animação hero e Core Web Vitals: mantenha LCP, CLS e INP no verde

Por equipe Charmotion · Atualizado em

Ilustração de capa do guia: Animação de hero e Core Web Vitals: mantenha LCP, CLS e INP no verde, com personagens de demonstração do Charmotion

Um hero animado não precisa prejudicar os Core Web Vitals se a página nunca esperar pela animação. Faça de uma imagem de poster estática o elemento de LCP, reserve o tamanho do hero com width e height ou aspect-ratio, e inicie o runtime da animação depois do carregamento, só quando o hero estiver visível e o movimento reduzido estiver desativado.

Principais pontos

  • Um canvas não é candidato a LCP, então um hero com Rive, canvas Lottie ou three.js deixa o LCP para a sua imagem de poster ou para o título. Faça esse poster pequeno, detectável no HTML e de alta prioridade.
  • Para um vídeo, o LCP usa o tempo de carregamento do poster ou o primeiro frame, o que vier antes, então um poster leve ajuda diretamente.
  • O layout shift vem da falta de dimensões: dê a todo contêiner de img, video e canvas um width e um height ou um aspect-ratio, e anime transform, não top nem left.
  • O INP sofre quando código de runtime roda durante o carregamento. Carregue o runtime no idle ou quando o hero aparecer na rolagem, e pause-o quando ele sair da tela.
  • Com prefers-reduced-motion, mantenha o poster e nem carregue o runtime.

Já tem um personagem ou logo? Veja exemplos em telas reais de app: telas de splash, onboarding, carregamento e estado vazio, com a animação que toca em cada uma.

Uma animação hero prejudica LCP, CLS ou INP?

Ela pode prejudicar os três, cada um de um jeito. Os Core Web Vitals do Google são medidos no percentil 75 de carregamentos reais de página, e cada um tem uma meta clara:

MétricaBomComo uma animação hero o prejudicaA solução
Largest Contentful Paint (LCP)2,5 s ou menos (ruim acima de 4,0 s)Um GIF pesado, um vídeo ou uma imagem que carrega tarde vira o maior elemento e chega atrasadoUma imagem de poster pequena como elemento de LCP, com fetch priority alta, nunca com lazy-load
Cumulative Layout Shift (CLS)0,1 ou menosO canvas ou o vídeo aparece sem espaço reservado e empurra o título para baixoAtributos width e height ou aspect-ratio; anime transform
Interaction to Next Paint (INP)200 ms ou menos (ruim acima de 500 ms)JavaScript de runtime, WebAssembly e montagem da cena rodam na main thread enquanto o usuário tenta tocarInicie o runtime no idle ou quando ficar visível; pause-o fora da tela

O web.dev define o INP como a latência de "todas as interações de clique, toque e teclado" durante uma visita, e observa que o atraso de input pode ser causado por "tarefas longas na main thread." Um hero que inicia uma cena 3D enquanto o visitante toca no botão de menu é um exemplo clássico de como chegar lá.

A boa notícia: nada disso exige uma página estática. Exige uma ordem de eventos. Pinte primeiro um frame parado, mantenha a caixa dele fixa e deixe o movimento começar quando o navegador tiver tempo.

Qual deve ser o elemento de LCP em um hero animado?

Uma imagem estática do primeiro frame do hero ou do frame em repouso, do mesmo tamanho da animação. O web.dev lista os tipos de elemento que o LCP considera: <img>, <image> dentro de SVG, <video>, elementos com imagem de fundo CSS url() e elementos de bloco que contêm texto. Um <canvas> não está nessa lista, o que importa para heroes com Rive, Lottie (renderer de canvas) e three.js: o canvas nunca vira o elemento de LCP, então o seu título ou poster vira.

Como cada formato é cronometrado:

  • Vídeo: o LCP usa "o tempo de carregamento da imagem de pôster ou o tempo de apresentação do primeiro frame dos vídeos", o que ocorrer primeiro. Um pôster leve vence.
  • GIF e PNG animado: usa-se o tempo de apresentação do primeiro frame, mas o navegador ainda precisa baixar uma parte suficiente de um arquivo grande para exibi-lo.
  • Runtimes em canvas: não contam, então o LCP mede a imagem de pôster sob o canvas ou o maior bloco de texto.

Depois, carregue esse pôster como qualquer imagem de LCP. O conselho do web.dev é direto: "Nunca use lazy-load na sua imagem de LCP", torne o recurso "detectável a partir do código HTML" e defina fetchpriority="high" nele (mas em no máximo uma ou duas imagens). Em um teste do Google Flights, o Fetch Priority levou o LCP de 2,6 s para 1,9 s. O pôster de um <video> não tem um atributo fetchpriority próprio, então, se o pôster for o seu elemento de LCP, faça o preload dele com alta prioridade:

<link rel="preload" as="image" href="/hero-poster.webp" fetchpriority="high">

<div class="hero-art">
  <img class="hero-poster" src="/hero-poster.webp" width="960" height="960"
       alt="Hushwave logo" fetchpriority="high">
  <canvas class="hero-canvas" width="960" height="960" aria-hidden="true" hidden></canvas>
</div>

Use um ou outro para uma mesma imagem: o <img> acima já está no HTML, então a linha de preload só é necessária quando o pôster for o pôster de um vídeo ou um background em CSS.

Como evitar que uma animação de hero cause layout shift?

Reserve o espaço antes que qualquer coisa carregue e mantenha a animação dentro dele. A regra do web.dev é: "Sempre inclua os atributos de tamanho width e height nos seus elementos de imagem e vídeo" e, quando não for possível, reserve o espaço com "CSS aspect-ratio ou algo parecido". Um canvas criado por script não tem tamanho intrínseco até o runtime definir um, então defina o tamanho no contêiner dele.

.hero-art {
  position: relative;
  width: min(100%, 480px);
  aspect-ratio: 1 / 1;
}
.hero-art > * {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

Como o pôster e o canvas ficam empilhados na mesma caixa, trocar um pelo outro não muda nada no layout. Mais três hábitos mantêm o CLS em zero:

  • Anime transforms. O web.dev lista animações de transform (translate, scale, rotate, skew) como seguras e avisa que "alterar as propriedades top e left também causa layout shifts". O mesmo vale para um logo que cresce alterando a largura.
  • Mantenha o overshoot dentro da caixa. Um mascote que pula ou se achata precisa que a altura do pulo caiba no contêiner, senão será cortado. Dimensione a artboard para a maior pose, não para a de repouso.
  • Nada de banners tardios acima do hero. Uma barra de cookies ou um aviso que empurra o hero para baixo depois de ele ser pintado também é um layout shift, seja qual for o conteúdo do hero.

Como carregar o runtime de animação sem prejudicar o INP?

Não o inicie durante a parte crítica do carregamento da página. O web.dev observa que "uma coisa que pode aumentar o atraso de entrada de uma interação enquanto a página carrega é a avaliação de scripts", e um runtime de animação é muito script: o runtime do Rive inclui um módulo WebAssembly, um player Lottie converte JSON em formas e uma cena three.js compila shaders e envia texturas.

Um padrão que funciona para todos eles: espere até o hero estar na tela (a API Intersection Observer existe para "decidir se executa ou não tarefas ou processos de animação com base em se o usuário vai ver o resultado"), depois espere um momento ocioso e só então importe o runtime.

const box = document.querySelector(".hero-art");
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

function whenIdle(fn) {
  if ("requestIdleCallback" in window) requestIdleCallback(fn, { timeout: 2000 });
  else setTimeout(fn, 200);
}

async function startHero() {
  const { Rive } = await import("@rive-app/canvas-lite");
  const canvas = box.querySelector(".hero-canvas");
  const r = new Rive({
    src: "/hero.riv",
    canvas,
    stateMachine: "State Machine 1",
    autoplay: true,
    onLoad: () => {
      r.resizeDrawingSurfaceToCanvas();
      canvas.hidden = false;
      box.querySelector(".hero-poster").hidden = true;
    },
  });
}

if (!reduce) {
  new IntersectionObserver((entries, observer) => {
    if (entries[0].isIntersecting) {
      observer.disconnect();
      whenIdle(startHero);
    }
  }).observe(box);
}

Observações sobre os detalhes:

  • requestIdleCallback não é Baseline (o MDN o marca como "Limited availability"), então o exemplo recorre a setTimeout. O MDN também recomenda um timeout para tarefas que precisam acontecer, por isso ele está definido como 2 segundos.
  • stateMachine (singular) é o parâmetro que o Rive adicionou no runtime v2.41.0; o plural stateMachines ainda funciona, mas está obsoleto.
  • Se você fizer preload do rive.wasm, a documentação do Rive avisa que a URL do preload e a URL passada para setWasmUrl() "devem ser exatamente iguais", senão o arquivo é baixado duas vezes. O preload também concorre com a sua imagem de LCP, então faça isso só quando o hero for o motivo da visita.
  • Pause fora da tela. Continue observando o hero depois que ele começar e chame r.pause() quando ele sair da viewport e r.play() quando voltar. As state machines do Rive também "se acomodam", parando o processamento quando nada muda. No three.js, renderer.setAnimationLoop(null) para o loop de renderização; passe sua função de loop de novo para retomar.
  • Vídeo precisa de menos disso. A decodificação roda no pipeline de mídia do navegador, então um <video> sem som custa principalmente banda, não script. O padrão de lazy-loading do web.dev usa preload="none" mais um Intersection Observer que inicia a reprodução quando o vídeo entra na viewport.

Qual é o peso de cada formato de animação de hero?

O peso importa mesmo quando a animação não é o elemento de LCP: ela concorre por banda com todo o resto da página e, nos dados móveis, é o dinheiro do visitante. Estes são números de primeira mão do nosso benchmark de tamanho de arquivos de animação (medido em 25 de outubro de 2026), para um loop idle de 4 segundos do mesmo personagem, no tamanho transferido:

FormatoMascote planoLogo 3D brilhanteTransparente
MP4 (H.264, 1080 px)211 KB558 KBNo
Runtime do Rive (373 KB brotli, uma vez por site) + .rivcerca de 385 KBcerca de 1,25 MBYes
WebM transparente (VP9 com alfa, 1080 px)500 KB2,0 MBYes
MOV HEVC transparente (1080 px)743 KB1,1 MBSim (Safari)
GIF (480 px)698 KB4,4 MBSó 1 bit

O que tirar disso:

  • GIF é o pior formato para hero. O próprio exemplo do web.dev comparava um GIF de 3,7 MB com um MP4 de 551 KB e um WebM de 341 KB do mesmo clipe.
  • Hero opaco sobre fundo sólido: o MP4 é o arquivo mais leve.
  • Vários estados ou interação com o ponteiro: Rive, porque cada estado extra são alguns kilobytes dentro de um único arquivo, em vez de mais um vídeo. O pacote canvas-lite do Rive é a variante menor; ele remove texto, layout, áudio, scripting e Vector Feathering.
  • Lottie e three.js: não medimos esses, e os dois dependem muito do seu build (qual player, quais módulos do three.js, o tamanho do modelo e das texturas). Confira o tamanho transferido no painel de rede do navegador antes de publicar.

Os conjuntos de personagem do Charmotion trazem um arquivo Rive com state machine, MP4, GIF, WebM e MOV transparentes, além de um pôster estático de cada animação, que é exatamente o pôster de que você precisa para o padrão de LCP acima. As animações de logo podem virar um hero de site que reage ao ponteiro, entregues com código web pronto para copiar e colar. Veja como funciona.

O que o hero deve mostrar quando o movimento reduzido está ativado?

O pôster, e nada mais. O MDN descreve prefers-reduced-motion como a configuração que informa ao navegador que o usuário "prefere uma interface que remova, reduza ou substitua animações baseadas em movimento". Ela está disponível em todos os navegadores desde janeiro de 2020, então não há motivo para ignorá-la.

O script acima já verifica isso antes de carregar o runtime, o que também economiza o download. Para um hero em vídeo, omita o atributo autoplay e inicie a reprodução pelo script só quando o movimento reduzido estiver desativado. Em CSS:

@media (prefers-reduced-motion: reduce) {
  .hero-canvas, .hero-video { display: none; }
  .hero-poster { display: block; }
}

Duas regras de acessibilidade valem para qualquer loop de hero, seja qual for a configuração:

  • Loops com mais de cinco segundos que começam sozinhos e ficam ao lado de outro conteúdo precisam de uma forma de pausar, parar ou ocultar (WCAG 2.2 SC 2.2.2, Nível A). Um hero que toca uma vez e para evita o problema.
  • Nada de piscar mais de três vezes em um segundo (SC 2.3.1). Varreduras rápidas de luz e partículas estroboscópicas em um hero 3D são os culpados de sempre.

O padrão completo, incluindo como trocar um loop em Rive ou vídeo por uma pose estática, está em animação acessível e movimento reduzido.

Como verificar se a animação de hero não prejudicou as Core Web Vitals?

Teste a página como um celular lento a vê, não como o seu notebook.

  1. Encontre o elemento de LCP. No Chrome DevTools, grave um carregamento de página no painel Performance e veja qual elemento está marcado como LCP. Deve ser seu pôster ou seu título, não um frame de vídeo nem uma imagem tardia.
  2. Confira as subpartes do LCP. O web.dev divide o LCP em tempo até o primeiro byte, atraso de carregamento do recurso, duração do carregamento do recurso e atraso de renderização do elemento. Um atraso de carregamento grande significa que o pôster foi descoberto tarde; uma duração de carregamento grande significa que ele é pesado demais.
  3. Procure layout shifts. A mesma gravação mostra as entradas de layout shift. Não deve haver nenhuma quando o canvas ou o vídeo substitui o pôster.
  4. Toque durante o carregamento. Com o throttling de CPU ativado, abra o menu enquanto o hero está iniciando. Se o menu travar, o runtime está começando cedo demais.
  5. Ative o movimento reduzido no sistema operacional e recarregue: o hero deve ser uma imagem estática e o runtime não deve aparecer no painel de rede.
  6. Acompanhe os dados de campo. Testes de laboratório são uma referência; o que conta é o percentil 75 das visitas reais, então compare o antes e o depois no seu monitoramento de usuários reais ou no Chrome UX Report.

Como adicionar uma animação de hero sem prejudicar as Core Web Vitals

  1. Exporte um pôster. Salve o primeiro frame ou o frame de repouso da animação como um WebP ou AVIF pequeno, no tamanho de exibição do hero.
  2. Faça do pôster o elemento de LCP. Coloque-o no HTML como um img com width, height e fetchpriority="high". Nunca use lazy-load nele.
  3. Reserve o espaço. Defina um aspect-ratio no contêiner do hero e empilhe o pôster e o canvas ou vídeo dentro dele com posicionamento absoluto.
  4. Inicie o runtime mais tarde. Importe o runtime do Rive, do Lottie ou do three.js só quando o hero estiver visível e o navegador estiver ocioso, e então esconda o pôster no callback de carregamento.
  5. Respeite o movimento reduzido. Se prefers-reduced-motion for reduce, mantenha o pôster e não carregue o runtime nem inicie o vídeo.
  6. Pause fora da tela e meça. Pause a animação quando ela sair da viewport e depois confira LCP, CLS e INP com throttling e nos dados de campo.

Perguntas frequentes

Um vídeo de hero com reprodução automática prejudica o LCP?

Pode prejudicar. Para um vídeo, o LCP usa o tempo de carregamento do pôster ou o tempo de apresentação do primeiro frame, o que ocorrer primeiro. Uma imagem de pôster leve mantém o LCP rápido mesmo que o arquivo de vídeo seja grande. Sem pôster, o navegador precisa buscar vídeo suficiente para mostrar o primeiro frame.

Um canvas do Rive ou do three.js conta como elemento de LCP?

Não. A lista de tipos de elemento de LCP do web.dev inclui imagens, elementos de imagem dentro de SVG, vídeos, imagens de fundo em CSS e blocos de texto, mas não canvas. Em um hero com canvas, o LCP costuma ser o seu título ou a imagem de pôster sob o canvas, então otimize esses.

Devo fazer preload do arquivo WebAssembly do Rive para um hero?

Só quando a animação for o ponto principal da página. O preload inicia o download mais cedo, mas concorre com a sua imagem de LCP. Se fizer o preload, a documentação do Rive diz que a URL do preload precisa ser exatamente igual à URL passada para setWasmUrl(), senão o arquivo é baixado duas vezes.

Fontes

Guias relacionados