滚动触发动画应使用 IntersectionObserver 而非 window.onscroll,因其轻量、不阻塞主线程、支持懒加载与反向触发;需合理配置 threshold 和 rootMargin 控制触发时机,添加动画类后及时 unobserve 防止重复播放,并配合 CSS 初始态与硬件加速属性实现流畅效果。

滚动触发动画的核心不是“等滚动完成”,而是实时监听元素进入视口的瞬间,用 IntersectionObserver 替代手动计算 scrollTop 和 getBoundingClientRect() —— 它更轻量、不阻塞主线程,且天然支持懒加载和反向触发。
为什么不用 window.onscroll 手动判断?
手动监听 scroll 事件容易掉帧、抖动,尤其在移动端;每次触发都要调用 element.getBoundingClientRect(),频繁重排重绘;边界条件难处理(比如元素初始就在视口内、页面缩放、iframe 嵌套)。
用 IntersectionObserver 是标准解法,浏览器原生优化,兼容性已覆盖 Chrome 64+、Firefox 55+、Safari 12.1+、Edge 79+。
- 不要在
scroll回调里反复调用getBoundingClientRect() - 避免用
setTimeout节流——IntersectionObserver自带防抖 - 若需兼容 IE,必须降级为
scroll + getBoundingClientRect,但要加throttle且只监听关键元素
IntersectionObserver 的关键配置项
触发时机由 threshold 和 rootMargin 共同决定,不是“一进就动”。比如想让动画在元素顶部到达视口底部时开始,就要调整这两个值。
立即学习“前端免费学习笔记(深入)”;
threshold 是一个数组,表示目标元素可见面积占比(0–1),常见取值:[0](只要露一点)、[0.1](10% 可见)、[0.5, 1.0](两个触发点)。
rootMargin 类似 CSS 的 margin,可写成 "0px 0px -100px 0px",负值表示提前触发(上边距 -100px = 元素还有 100px 进入视口时就通知)。
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate-in');
}
});
},
{
threshold: [0.1],
rootMargin: '0px 0px -50px 0px' // 提前 50px 触发
}
);
动画类名添加后如何防止重复触发?
默认情况下,元素滚出视口再滚回,isIntersecting 会再次为 true,导致动画重复播放。需要手动控制状态或使用 observer.unobserve()。
- 简单场景:添加类后立刻
unobserve,确保只执行一次 - 循环动画(如悬停态恢复):用
entry.intersectionRatio === 0判断完全离开,再移除类 - 慎用
animation-play-state控制暂停——它不重置动画状态,可能卡在中间帧
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate-fade-up');
observer.unobserve(entry.target); // 关键:只触发一次
}
});
});
CSS 动画配合的注意事项
JS 控制类名,CSS 负责表现。但很多动画失效是因为忽略了 transform 和 opacity 的硬件加速前提,或没设初始态。
- 必须给元素设置初始样式(如
opacity: 0; transform: translateY(20px);),否则加类瞬间无过渡 - 动画属性优先用
transform和opacity,避免触发布局(height、left等) - 用
will-change: transform提前提示浏览器优化(仅对高频动画元素) - 移动端注意
prefers-reduced-motion,可用@media (prefers-reduced-motion: reduce)关闭动画
真正难的不是让动画动起来,而是让它们在正确的时间、以正确的节奏、不干扰用户滚动体验地动起来——IntersectionObserver 的 rootMargin 和 threshold 组合,比想象中更敏感,微调 10px 或 0.05 就可能让动效“卡半拍”或“抢跑”。











