IntersectionObserver 是触发入场动画最稳最轻量的方案,它异步回调、自动节流、不卡顿不误判,需在元素挂载后立即 observe,配合 opacity:0/translateY() 初始态和 animation-fill-mode:forwards 防闪防回退。

用 IntersectionObserver 触发入场动画是当前最稳、最轻量的方案,比手写 scroll 事件监听靠谱得多——它不卡顿、不误判、自动节流,而且浏览器原生支持。
为什么不能直接用 CSS animation 加载就播
CSS 的 @keyframes + animation 一加载就执行,和用户是否看见元素完全无关。你看到“闪一下再动”或“滚动到一半才开始”,基本都是这个原因。入场动画必须等元素真正进入视口才启动,而 IntersectionObserver 就是干这事的:只管“有没有进”,不管“滚了多少”。
- 别在
window.addEventListener('scroll', ...)里反复调用getBoundingClientRect()—— 主线程压力大,低端机明显掉帧 -
IntersectionObserver是异步回调,浏览器内部优化过,iOS Safari 12.1+、Chrome 51+、Firefox 55+ 都已稳定支持 - IE 完全不支持,如需兼容,得用 polyfill 或降级为 scroll 监听(但仅限兜底)
IntersectionObserver 初始化时最容易漏的两件事
初始化晚了或选错元素,会导致首次进入视口时不触发——尤其在 SPA 或动态渲染内容时特别明显。
- 必须在目标元素已经挂载到 DOM 后立刻调用
observer.observe(el),不能等DOMContentLoaded或window.onload才开始 - 要用
document.querySelectorAll('.js-animate'),不是document.querySelector('.js-animate')—— 后者只返回第一个匹配元素,其余全被忽略 - 如果元素是 JS 动态插入的(比如 React 渲染后),得在插入后立即 observe,不能靠一次性遍历
动画类怎么写才不闪、不跳、不回退
初始状态必须「占位但不可见」,否则会先渲染出原始样式,再突兀跳到动画起点,视觉上就是“闪一下”。
立即学习“前端免费学习笔记(深入)”;
- 推荐组合:
opacity: 0+transform: translateY(20px)+visibility: hidden(可选) - 千万别用
display: none做初始隐藏——它会让元素脱离文档流,导致后续布局重排、文字抖动 - 动画结束要停在最后一帧,必须加
animation-fill-mode: forwards或在 class 里写forwards,否则动画播完瞬间回退到初始态 - 避免在动画中改
height、width、top、left等触发布局的属性,它们无法 GPU 加速
怎么防止同一元素反复触发、重复播放
IntersectionObserver 默认不记忆状态,元素进出多次,回调就执行多次。你得自己控制“只播一次”。
- 最简做法:在回调里加个判断
if (el.classList.contains('animate-in')) return;,然后加类、再调observer.unobserve(el) - 别依赖
animationend事件来清理——切 Tab、快速滚动、系统休眠都可能导致它不触发 - 如果需要“退出后再进入重播”,就去掉
unobserve,改用类名切换逻辑(比如animate-in active和animate-in之间来回切) - 注意:一旦
unobserve,该元素就不会再被监听,除非你手动重新observe
真正容易被忽略的是 threshold 和 rootMargin 的配合使用——比如想让动画提前一点触发,设 threshold: 0.1 不够,还得加 rootMargin: '0px 0px -50px 0px' 把触发区往上扩,否则首屏元素可能直接“跳过”动画。这个细节在移动端尤其关键。



















