¡Hola Devs! Olvídate del IntersectionObserver 🚀
Si alguna vez te has pasado una tarde entera escribiendo callbacks de IntersectionObserver solo para que un div haga fade-in al entrar en pantalla, esto te va a interesar. Y mucho.
La nueva propiedad animation-trigger hace exactamente eso: retrasa el inicio de una animación CSS hasta que se dispare un trigger con nombre — ya sea por posición de scroll, intersección con el viewport, o incluso un evento del DOM como un click.
.element {
/* Espera a que --trigger se active, reproduce al entrar y al revés al salir */
animation: fade-in 0.35s ease-in-out both;
animation-trigger: --trigger play-forwards play-backwards;
}
Una sola línea reemplaza un bloque bastante grande de JS. Vamos a ver cómo funciona de verdad.
⚠️ Ojo, Dev: la spec sigue en Editor's Draft y solo corre en Chrome 145+. Trátalo como experimental hasta que haya soporte más amplio.
El artículo original sobre animation-trigger es la 근거자료 por si quieres profundizar en la spec.

Cómo funciona animation-trigger de verdad
Sintaxis
animation-trigger: none | <nombre-trigger> <acción> [<acción>];
none— por defecto; la animación se comporta normal.<nombre-trigger>— un dashed ident (ej.:--fade-in-trigger) que debe coincidir con un nombre definido portimeline-triggeroevent-trigger.<acción>— qué hacer cuando el trigger se activa (y opcionalmente, cuando se desactiva).
Las acciones disponibles
| Acción | Comportamiento |
|---|---|
play-forwards | Reproduce hacia adelante |
play-backwards | Reproduce al revés |
play | Reproduce a la velocidad actual |
play-once | Solo desde el estado inicial/pausado; ignora si ya terminó |
pause | Congela en el lugar |
reset | Vuelve el progreso a 0 y pausa |
replay | Vuelve a 0 y reproduce de inmediato |
none | No hace nada |
Algunas acciones no son exclusivas — puedes combinar play-backwards al entrar con play-forwards al salir para efectos reversibles.
Configurando un timeline-trigger
No puedes usar animation-trigger solo. Primero necesitas una fuente de trigger:
.trigger {
/* Shorthand: nombre | fuente | activation-range / active-range */
timeline-trigger: --trigger scroll() contain / cover;
}
Separado en longhands:
.timeline-trigger-name: --fade-in;
timeline-trigger-source: view();
timeline-trigger-activation-range: contain;
timeline-trigger-active-range: cover;
activation-range— cuándo se enciende el trigger (ej.:contain= elemento totalmente visible).active-range— el límite externo donde el trigger sigue activo. Si lo omites, hereda el activation range.- El active range debe incluir el activation range, o el trigger nunca se enciende.
A diferencia de background o border, el orden del shorthand es fijo. No lo revuelvas.
Enganchándolo a la animación
/* El trigger vive en un elemento centinela padre */
.trigger {
timeline-trigger: --trigger scroll() contain / cover;
}
/* La animación aplica a cuantos hijos quieras */
.text {
animation-trigger: --trigger play;
animation: fade 0.6s ease-out;
}
Como trigger y animación están desacoplados, un trigger puede manejar varios elementos. Pones un centinela al inicio de una sección y todos los hijos animan juntos al entrar al viewport — sin observer por elemento.
Scope de triggers
Por defecto, los nombres de trigger son globales. Si dos elementos declaran --trigger, el último en la cascada gana. Usa trigger-scope para restringirlo a un subtree del DOM — esencial para componentes reutilizables.

Scroll-triggered vs. scroll-driven: NO son lo mismo
Esta es la confusión más común. Los nombres se parecen, los dos usan scroll, pero resuelven problemas distintos.
| Aspecto | Scroll-Driven | Scroll-Triggered |
|---|---|---|
| Modelo de progreso | Continuo, atado al offset del scroll | Estado binario (on/off) |
| Timing | Scrubbing frame a frame con el scroll | Dispara una vez, luego corre como animación CSS normal |
| ¿Reversible? | Sí, naturalmente | Solo si defines acción de desactivación |
| Uso típico | Parallax, barras de progreso, reveals sticky | Fade-ins, reveals puntuales, llamar la atención |
| Propiedad clave | animation-timeline: scroll() | animation-trigger: --x play |
En resumen: scroll-driven = scrub, scroll-triggered = dispara. Si necesitas rebobinar conforme subes, es scroll-driven. Si necesitas un momento discreto de "entró, reproduce", es trigger.
Cuidados que NO puedes ignorar
- Solo Chrome 145+. Firefox y Safari aún no lo entregan. Cualquier uso en producción hoy requiere feature query o fallback a
IntersectionObserver. - Estado de Editor's Draft. La spec puede cambiar. Nombres, keywords y orden del shorthand no están congelados.
- Scope global por defecto. Fácil de colisionar en codebases grandes; siempre usa
trigger-scopeen componentes. - No hay
animation-triggersin fuente. Aún necesitastimeline-triggeroevent-triggerconfigurado. - El debug es complicado. El soporte de DevTools para rangos de activación todavía está madurando.
Si te late mantener pipelines con "human-in-the-loop" en proyectos open source, los mismos principios de scope y fallback aplican — checa el pipeline semanal de huggingface_hub para un paralelo en CI.

¿Dónde nos deja esto?
animation-trigger es la dirección correcta para la plataforma web: menos observers en JS, más CSS declarativo, y una separación limpia entre qué anima y cuándo se dispara. Solo el desacople trigger/animación ya vale la pena — un centinela, varios hijos animando.
Pero no está listo para producción. Solo Chrome, Editor's Draft, y sin degradación graciosa incluida. La jugada pragmática hoy es:
- Prototipa con él. Familiarízate con la sintaxis ahora para estar listo cuando Safari y Firefox entreguen.
- Usa feature query. Envuelve el uso en
@supports (animation-trigger: none)y mantén vivo el fallback conIntersectionObserver. - Scopéa tus triggers. No dejes que nombres globales se vuelvan el próximo
!important.
Qué estudiar después
- Scroll-driven animations (
animation-timeline: scroll()) — la spec hermana que querrás en el mismo kit. trigger-scope— para aislar triggers dentro de Web Components.- Event triggers — la spec también soporta eventos del DOM, no solo timeline.