配了三年IntersectionObserver,今天才发现它早就可以被浏览器自己替换了——animation-trigger把这件事彻底变了
写滚动入场动画,你大概也是这么干的:写一个 IntersectionObserver,监听元素进入视口,然后调 element.animate() 播放动画,最后 unobserve 防止重复触发。这套写法跑了快十年,没人觉得有问题。
Chrome 146 把这件事彻底变了。
问题的根子在哪
IntersectionObserver 本身不占主线程,但它触发的是一段 JavaScript,这段 JS 里调 element.animate() 是 Web Animations API,每次执行都要从浏览器渲染管道里绕一圈。对于滚动这种高频事件,”触发了然后做什么”才是真正开销所在。
更关键的是:IntersectionObserver 的语义是”观测”,它只告诉你”进了还是出了”,动画播放逻辑完全是 JS 自己兜着的。同一个动画效果,两拨人可能写出完全不同的 observer 代码,维护成本高,还容易出 bug。
animation-trigger 是什么
Chrome 146 新引进的 CSS 属性,叫 Scroll-Triggered Animations(滚动触发动画),和已有的 Scroll-Driven Animations 是两码事。
Scroll-Driven: 动画进度和滚动位置完全绑定,滚到哪动画就到哪,滚回去就倒回来,没有”播放完”这个概念。
Scroll-Triggered: 滚动到某个阈值时触发一次,然后这个动画按自己的时长播完,不管后面滚到哪。触发一次,播一次,跟 IntersectionObserver 加 element.animate() 的语义完全一样,但是纯 CSS。
语法长这样:
/* 先定义触发器,命名随便起 */
.trigger-target {
timeline-trigger-name: --reveal;
timeline-trigger-source: view();
timeline-trigger: --reveal scroll() contain / cover;
}
/* 然后在要动画的元素上引用它 */
.reveal-card {
animation: fade-up 0.6s ease-out both;
animation-trigger: --reveal play-forwards;
}
@keyframes fade-up {
from { opacity: 0; translate: 0 32px; }
to { opacity: 1; translate: 0 0; }
}
11 行 JS 变成了 5 行 CSS。
一套语法,多种行为
animation-trigger 接受的动作关键词很丰富,不只是 play-forwards:
play-forwards:进入触发,播完停住(最常用,等同 IntersectionObserver 场景)play-backwards:离开触发,反向播完(适合”滑出去”动画)play-forwards play-backwards:进播、离反,适合需要双向动效的场景pause、reset、replay:手动控制动画状态
timeline-trigger 的范围写法也灵活:contain 是元素完全进入视口才触发,cover 是元素开始覆盖视口就触发,entry / exit 分别对应进和出的时刻。混搭可以做出非常精细的控制,比如”元素完全进入视口时播,离开时反向收”。
和 scroll-driven 怎么选
判断标准很简单:你要的是”滚动到哪里,动画就到哪里”(进度同步),还是”滚动到这里,动画开始播,播完就停”(状态触发)?
前者用 animation-timeline: scroll() 或 animation-timeline: view(),对应 Scroll-Driven;后者用 timeline-trigger + animation-trigger,对应 Scroll-Triggered。
两者可以共存,不互斥。实际项目中,同一个列表可能同时用到了:视差背景用 Scroll-Driven(和滚动同步),卡片入场用 Scroll-Triggered(进页面播一次就够)。
@supports 渐进增强
目前只有 Chrome 146+ 支持,Safari 和 Firefox 暂无。生产使用要加降级:
.reveal-card {
opacity: 1;
transform: translateY(0);
}
@supports (animation-trigger: play-forwards) {
.reveal-card {
opacity: 0;
transform: translateY(32px);
animation: fade-up 0.6s ease-out both;
animation-trigger: --reveal play-forwards;
timeline-trigger-name: --reveal;
timeline-trigger-source: view();
}
}
不支持的浏览器直接显示最终状态,支持的浏览器播进入动画。功能不退化,只是没动效。
accessibility 注意
所有 CSS 动画都受 prefers-reduced-motion 控制,在媒体查询里加上就行:
@media (prefers-reduced-motion: reduce) {
.reveal-card {
animation: none;
opacity: 1;
transform: none;
}
}
下一步
如果你现在正在给项目里的卡片入场动画配 IntersectionObserver,可以停下来试试这个思路:先确认你的项目有没有 targeting Chrome 146+ 的条件(用 browserslist 或者 Canary 测试),有的话直接换 CSS,写完测试一下 Safari 和 Firefox 下的降级是否正常——如果不需要动效,只是”显示内容”,降级本身就该是最终状态。
旧代码不需要删,等浏览器支持跟上去了,IntersectionObserver 那套 observer 逻辑可以整体下掉。
核心证据来源:CSS-Tricks – animation-trigger (2026-08-26),modern-css.com – Scroll-Triggered Animations,daily.dev – A First Look at Scroll-Triggered Animations
评论区
登录后可评论。