12k
All articles

Disparando animações CSS na rolagem sem JavaScript

Crie animações CSS acionadas pela rolagem sem JavaScript. Veja a sintaxe, as ações dos gatilhos, os limites e a alternativa com IntersectionObserver.

OpenReplay Team
OpenReplay Team
Disparando animações CSS na rolagem sem JavaScript

Animações acionadas por rolagem (scroll-triggered animations) deixam a rolagem decidir quando uma animação CSS comum começa. Assim que um elemento atinge uma posição de rolagem definida, a animação é executada com seu próprio relógio. Você as declara com as propriedades timeline-trigger e animation-trigger, sem precisar de IntersectionObserver.

A maior parte do código de “fade in ao rolar” é igual em todo lugar: um observer, um threshold, a alternância de uma classe e uma chamada a unobserve() que alguém esquece de adicionar. Agora esse trabalho pode ser feito em CSS.

Este artigo aborda a sintaxe, um exemplo completo de revelação, os três comportamentos de trigger, como escolher os limites de rolagem e como colocar o recurso em produção com segurança em um cenário em que só o Chromium oferece suporte.

Principais conclusões

  • Uma animação acionada por rolagem precisa de duas declarações. timeline-trigger dá nome a um trigger e define o intervalo de rolagem que o ativa. animation-trigger indica a uma animação qual trigger seguir e o que fazer quando ele dispara.
  • O intervalo entry 100% exit 0% ativa um trigger somente enquanto o elemento está totalmente dentro do scrollport. A documentação do Chrome o trata como equivalente à palavra-chave contain.
  • play-forwards play-backwards executa a animação na entrada e a reverte na saída. play-once, do rascunho de Animation Triggers, a executa uma única vez.
  • Uma animação acionada mantém sua própria duração e easing, então rolar mais rápido não faz com que ela seja executada mais rápido.
  • Os dados de suporte do Chrome listam apenas navegadores Chromium para animações acionadas por rolagem, então oculte o conteúdo dentro de @supports e mantenha um fallback com IntersectionObserver.

Qual é a diferença entre animações orientadas por rolagem e animações acionadas por rolagem?

Animações orientadas por rolagem (scroll-driven) vinculam o progresso de uma animação à posição de rolagem. Animações acionadas por rolagem (scroll-triggered) usam a rolagem apenas para decidir quando uma animação comum começa. Nosso guia sobre como criar animações de rolagem apenas com CSS aborda o primeiro modelo: animation-timeline com scroll() e view(). Nele, a timeline substitui o relógio, então rolar de volta executa a animação ao contrário. Este artigo aborda timeline-trigger e animation-trigger, que iniciam uma animação comum, com sua própria duração, assim que o elemento atinge um intervalo de rolagem escolhido.

Se você quer uma barra de progresso de leitura ou um efeito parallax, use animações CSS de progresso de rolagem. Se você quer que um card apareça com fade in ao longo de 400ms assim que ficar visível, use um trigger. Em seu resumo de Web UI do Google I/O 2026, o Chrome lista os dois como recursos separados e descreve os triggers como uma forma de substituir o IntersectionObserver por CSS. Em resumo, um trigger é a versão em CSS de “adicionar uma classe quando este elemento ficar visível”.

Um exemplo de fade-in acionado por rolagem

Um fade-in acionado por rolagem precisa de três partes: uma regra @keyframes, uma animation com fill forwards e um trigger que vincula a animação a uma view timeline. O anúncio do Chrome sobre animações acionadas por rolagem define a sintaxe. Os pontos principais são que o trigger e a animação podem ficar em elementos diferentes e que um trigger usa uma scroll progress timeline ou uma view progress timeline como origem.

<section class="reveal">
  <div class="reveal-content">
    <h2>Shipping notes</h2>
    <p>This block fades and slides in once it is fully on screen.</p>
  </div>
</section>
@keyframes reveal {
  to {
    opacity: 1;
    translate: 0 0;
  }
}

@supports (timeline-trigger-name: --t) {
  @media (prefers-reduced-motion: no-preference) {
    .reveal {
      timeline-trigger: --reveal view() entry 100% exit 0%;
      trigger-scope: --reveal;
    }

    .reveal-content {
      opacity: 0;
      translate: 0 2rem;
      animation: reveal 400ms ease-out forwards;
      animation-trigger: --reveal play-forwards;
    }
  }
}

Como funciona:

  • O trigger fica na section e o movimento fica no elemento filho. O elemento que desliza não é o elemento cuja visibilidade é medida, então o deslocamento de translate não consegue mover o limite do trigger.
  • entry 100% exit 0% ativa o trigger assim que a section está totalmente dentro do scrollport.
  • play-forwards inicia a animação toda vez que o trigger é ativado. O fill forwards mantém o keyframe final depois disso.
  • O estado inicial oculto fica dentro da feature query. Navegadores sem suporte nunca aplicam opacity: 0, então o conteúdo permanece visível.
  • trigger-scope mantém o nome --reveal de cada section como local. Por padrão, qualquer elemento da página consegue enxergar o nome de um trigger, da mesma forma que anchor-name funciona, conforme descrito nas notas de versão do Chrome 146.

Com apenas play-forwards, sair do intervalo não faz nada: o elemento mantém seu estado final enquanto você rola para longe. Quando ele fica totalmente visível de novo, o trigger é ativado novamente e a animação é reexecutada. A próxima seção mostra como mudar isso.

Escolhendo uma ação de trigger

animation-trigger aceita um nome de trigger seguido de até duas ações. A primeira é executada quando o trigger é ativado e a segunda quando ele é desativado. A ação escolhida define o que acontece na reentrada e na saída.

Comportamento desejadoValor de animation-triggerObservação
Reexecutar a cada reentrada--reveal play-forwardsPode saltar visivelmente de volta ao estado inicial
Reverter na saída--reveal play-forwards play-backwardsApós reverter, o elemento exibe seus estilos base
Executar uma vez e permanecer--reveal play-oncePalavra-chave do rascunho, pode mudar
animation-trigger: --reveal play-forwards play-backwards; /* reverses on exit */
animation-trigger: --reveal play-once; /* runs once; Animation Triggers draft keyword */

O fill forwards mantém o keyframe to depois que play-forwards termina. Ele não mantém nada depois que play-backwards termina. Nesse ponto, a animação deixa de ser aplicada e o elemento volta aos seus estilos base. Neste exemplo, os estilos base são o estado inicial oculto (opacity: 0; translate: 0 2rem), porque os keyframes definem apenas to, então o elemento ainda termina oculto. Se seus keyframes definem um estado from diferente dos estilos base, use both para que esse estado seja mantido após a reversão. play-once vem do Editor’s Draft de CSS Animation Triggers Level 1, e não do anúncio do Chrome, então verifique-o na versão do Chrome que você tem como alvo antes de depender dele. É o equivalente mais próximo de chamar unobserve() após a primeira interseção.

Intervalo de ativação vs. intervalo ativo

Em um trigger de rolagem, o intervalo de ativação decide quando o trigger dispara. O intervalo ativo opcional, escrito após uma barra, decide por quanto tempo o trigger permanece ativo, e o Chrome exige que ele englobe o intervalo de ativação. Segundo o Chrome, entry 100% exit 0% equivale a contain e entry 0% exit 100% equivale a cover, então estas duas linhas são idênticas:

timeline-trigger: --reveal view() entry 100% exit 0% / entry 0% exit 100%;
timeline-trigger: --reveal view() contain / cover;

Com essa configuração, o trigger é ativado assim que o elemento está totalmente visível e permanece ativo até que nenhuma parte dele esteja visível. Isso é importante para play-backwards, porque a animação reversa aguarda até que o elemento saia da tela.

Escolhendo limites de rolagem adequados

O intervalo de trigger de rolagem correto depende do tamanho do elemento e de qualquer elemento fixo de interface que cubra a viewport.

SituaçãoIntervaloMotivo
Card ou título pequenoentry 100% exit 0% (contain)Dispara assim que o elemento inteiro está visível
Section altaentry 25% exit 0%contain pode disparar tarde demais para elementos altos
Sob um header sticky de 4remview(block 4rem 0)Impede que o header oculte o elemento
/* Tall element: fire when a quarter has entered */
timeline-trigger: --reveal view() entry 25% exit 0%;

/* 4rem sticky header: start (top) inset first */
timeline-trigger: --reveal view(block 4rem 0) entry 100% exit 0%;

Em view(), o inset inicial vem antes do inset final. Em um eixo de bloco vertical, o início é o topo, então a altura de um header no topo vem primeiro.

Suporte dos navegadores e fallbacks

O resumo de Web UI do Google I/O 2026 do Chrome indica suporte a animações acionadas por rolagem no Chrome e no Edge 146, sem suporte no Firefox nem no Safari. O anúncio do Chrome de dezembro de 2025 dizia que o recurso chegaria no Chrome 145, mas ele foi lançado no Chrome 146 (versão estável de 10 de março de 2026). O post sobre o Chrome 146 beta lista trigger-scope na mesma versão. Trate o recurso inteiro como melhoria progressiva.

O wrapper @supports do exemplo cuida disso. Navegadores sem suporte ignoram o bloco inteiro e renderizam o conteúdo de forma estática. Se você quiser o mesmo movimento em outros navegadores, mantenha sua alternância de classe com IntersectionObserver fora da feature query e execute-a apenas quando CSS.supports() retornar false. Nosso artigo sobre como substituir bibliotecas de animação por APIs nativas da web mostra como construir esse fallback sem dependências no estilo do AOS.

A query prefers-reduced-motion: no-preference garante que usuários que pedem menos movimento vejam o conteúdo imediatamente. Para escalonar vários elementos, aponte o animation-trigger de cada um para um único trigger compartilhado, com delays diferentes.

Conclusão

Animações acionadas por rolagem cobrem o padrão de fade-in ao rolar: o CSS decide quando uma animação comum começa e o navegador faz a observação. Comece pelo exemplo acima. Escolha a ação (reexecutar, reverter ou executar uma vez) que combina com seu design, ajuste o intervalo para elementos altos ou headers sticky e remova o código do observer nos navegadores com suporte, mantendo-o para os demais.

Perguntas frequentes

Qual é a diferença entre animações acionadas por rolagem e scroll-state container queries?

Scroll-state container queries aplicam estilos enquanto um container está em um estado de rolagem gerenciado pelo navegador. Animações acionadas por rolagem iniciam uma animação de keyframes quando um elemento cruza um intervalo em uma view timeline ou scroll timeline. O Chrome 133 lançou os estados stuck, snapped e scrollable, e o Chrome 144 adicionou scrolled, que acompanha a direção de rolagem mais recente. Use uma scroll-state query para um header sticky que muda de estilo ao ficar fixo, e um trigger para efeitos de revelação.

Animações acionadas por rolagem são mais rápidas que uma revelação com IntersectionObserver?

As notas de versão do Chrome dizem que, como a interação é descrita em CSS, o navegador pode transferir o trabalho para uma worker thread. Uma revelação com IntersectionObserver executa seu callback e a alternância de classe na main thread, então trabalho pesado de script pode atrasá-la. O Chrome descreve essa transferência como um benefício de design, não como um resultado medido, então faça o profiling das suas próprias páginas antes e depois da mudança em vez de presumir um ganho.

Triggers de rolagem podem controlar animações criadas com a Web Animations API?

Sim. As notas de versão do Chrome 146 dizem que o recurso vem acompanhado de interfaces JavaScript, então os triggers podem controlar animações criadas com a Web Animations API, e não apenas animações CSS. Os nomes das interfaces estão definidos no Editor's Draft de CSS Animation Triggers Level 1, que ainda pode mudar, então verifique o rascunho e a versão do Chrome que você tem como alvo antes de depender deles.

Por que meu elemento continua oculto depois de adicionar um trigger de rolagem?

O estado inicial oculto é aplicado assim que a feature query é satisfeita, então o elemento permanece com opacity 0 até que seu trigger seja ativado. Causas comuns são uma diferença de grafia entre o nome em timeline-trigger e o nome em animation-trigger, um elemento animado fora da subárvore definida por trigger-scope ou um intervalo que o elemento nunca atinge. Verifique o nome primeiro e depois confirme se o elemento animado está dentro do elemento de escopo.

Open-source session replay

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.