Olha só isso: o CSS vai aposentar seu IntersectionObserver 🚀
Se você já passou horas escrevendo callbacks de IntersectionObserver só pra fazer um elemento aparecer com fade quando entra na viewport, respira fundo. Vem aí uma solução nativa.
A nova propriedade animation-trigger faz exatamente isso: ela atrasa o início de uma animação CSS até que um trigger nomeado seja acionado — seja por posição de scroll, interseção de viewport ou até evento do DOM (tipo um clique).
.element {
/* Espera o --trigger ativar, toca pra frente ao entrar e pra trás ao sair */
animation: fade-in 0.35s ease-in-out both;
animation-trigger: --trigger play-forwards play-backwards;
}
Uma linha substitui um bloco considerável de JS. Bora entender como isso funciona de verdade.
⚠️ Atenção, dev: a spec ainda está em Editor's Draft e só roda no Chrome 145+. Trate como experimental até ter suporte mais amplo.
O artigo original sobre animation-trigger é a 근거자료 se você quiser se aprofundar na spec.

Como o animation-trigger funciona de verdade
Sintaxe
animation-trigger: none | <nome-do-trigger> <ação> [<ação>];
none— padrão; a animação se comporta normalmente.<nome-do-trigger>— um dashed ident (ex.:--fade-in-trigger) que precisa bater com um nome definido portimeline-triggerouevent-trigger.<ação>— o que fazer quando o trigger ativa (e opcionalmente, quando desativa).
As ações disponíveis
| Ação | Comportamento |
|---|---|
play-forwards | Toca pra frente (playback rate positivo) |
play-backwards | Toca ao contrário |
play | Toca na taxa atual |
play-once | Toca só do estado inicial/pausado; ignora se já terminou |
pause | Congela no lugar |
reset | Volta o progresso pra 0 e pausa |
replay | Volta pra 0 e toca na hora |
none | Não faz nada |
Algumas ações não são exclusivas — dá pra combinar play-backwards na entrada com play-forwards na saída pra efeitos reversíveis.
Configurando um timeline-trigger
Você não usa animation-trigger sozinho. Primeiro precisa de uma fonte de trigger:
.trigger {
/* Shorthand: nome | fonte | activation-range / active-range */
timeline-trigger: --trigger scroll() contain / cover;
}
Separando em longhands:
.timeline-trigger-name: --fade-in;
timeline-trigger-source: view();
timeline-trigger-activation-range: contain;
timeline-trigger-active-range: cover;
activation-range— quando o trigger liga (ex.:contain= elemento totalmente visível).active-range— o limite externo onde o trigger continua ativo. Se omitir, herda o activation range.- O active range precisa incluir o activation range, senão o trigger nunca liga.
Diferente de background ou border, a ordem do shorthand é fixa. Não embaralha.
Ligando na animação
/* Trigger fica num elemento sentinela pai */
.trigger {
timeline-trigger: --trigger scroll() contain / cover;
}
/* Animação aplica em quantos filhos quiser */
.text {
animation-trigger: --trigger play;
animation: fade 0.6s ease-out;
}
Como trigger e animação são desacoplados, um trigger pode dirigir vários elementos. Coloca um sentinela no topo da seção e todos os filhos animam juntos quando entram na viewport — sem observer por elemento.
Escopo de triggers
Por padrão, nomes de trigger são globais. Se dois elementos declaram --trigger, o último na cascata vence. Use trigger-scope pra restringir a um subtree do DOM — essencial pra componentes reutilizáveis.

Scroll-triggered vs. scroll-driven: NÃO é a mesma coisa
Essa é a confusão mais comum. Os nomes parecem iguais, os dois usam scroll, mas resolvem problemas diferentes.
| Aspecto | Scroll-Driven | Scroll-Triggered |
|---|---|---|
| Modelo de progresso | Contínuo, atrelado ao offset do scroll | Estado binário (on/off) |
| Timing | Scrubbing frame a frame com o scroll | Dispara uma vez, depois roda como animação CSS normal |
| Reversível? | Sim, naturalmente | Só se você definir ação de desativação |
| Uso típico | Parallax, barras de progresso, reveals sticky | Fade-ins, reveals pontuais, chamar atenção |
| Propriedade chave | animation-timeline: scroll() | animation-trigger: --x play |
Resumindo: scroll-driven = scrub, scroll-triggered = dispara. Se precisa rebobinar conforme sobe, é scroll-driven. Se precisa de um momento discreto de "entrou, toca", é trigger.
Cuidados que você NÃO pode ignorar
- Só Chrome 145+. Firefox e Safari ainda não entregaram. Qualquer uso em produção hoje precisa de feature query ou fallback pra
IntersectionObserver. - Status de Editor's Draft. A spec pode mudar. Nomes, keywords e ordem do shorthand não estão congelados.
- Escopo global por padrão. Fácil de colidir em codebases grandes; sempre use
trigger-scopeem componentes. - Sem
animation-triggersem fonte. Você ainda precisa detimeline-triggerouevent-triggerconfigurado. - Debug é chato. O suporte do DevTools pra ranges de ativação ainda está amadurecendo.
Se você curte pipelines com "human-in-the-loop" em projetos open source, os mesmos princípios de escopo e fallback se aplicam — dá uma olhada no pipeline semanal do huggingface_hub pra ver um paralelo em CI.

Onde isso nos deixa
animation-trigger é a direção certa pra plataforma web: menos observers em JS, mais CSS declarativo e uma separação limpa entre o que anima e quando dispara. Só o desacoplamento trigger/animação já vale o preço — um sentinela, vários filhos animando.
Mas não tá pronto pra produção. Só Chrome, Editor's Draft, sem degradação graciosa embutida. O jogo pragmático hoje é:
- Prototipe com ele. Familiarize-se com a sintaxe agora pra estar pronto quando Safari e Firefox entregarem.
- Use feature query. Envolva o uso em
@supports (animation-trigger: none)e mantenha o fallback comIntersectionObservervivo. - Escopar triggers. Não deixe nomes globais se tornarem o próximo
!important.
O que estudar depois
- Scroll-driven animations (
animation-timeline: scroll()) — a spec irmã que você vai querer no mesmo kit. trigger-scope— pra isolar triggers dentro de Web Components.- Event triggers — a spec também suporta eventos do DOM, não só timeline.