ReactJS 结合react-use Intersection Observer实现GSAP单次入场动画
问题根源定位
按教程注释淡出逻辑不生效,和淡出代码本身没关系,核心问题出在原有判断逻辑没有状态记忆:
- 现有三元表达式是每次组件重渲染都会重新执行的:交叉观察器只要状态变化就会触发组件更新,只要元素交叉比例≥0.4,就会无差别执行一次
fadeIn。哪怕fadeOut是空函数,高频重复调用GSAP的补间方法,会导致正在播放的动画被反复打断,视觉上出现卡顿、闪烁,看起来和没写逻辑一样。 - 交叉观察器的状态是随滚动连续变化的:元素在视口内移动时,交叉比例会实时波动,没有触发锁的情况下,入场逻辑会被持续反复调用。
实现方案
完全可以用if判断替代原有三元逻辑实现“首次进视口才播一次动画”的需求,核心要加一个不触发额外重渲染的触发锁,避免重复执行动画,具体写法:
- 用
useRef存储动画是否已触发的标记,相比useState,修改Ref的值不会触发组件额外重渲染,性能更好 - 逻辑优先检查锁状态,只要动画已经触发过,后续所有交叉状态变化都直接跳过
- 直接用
useIntersection官方提供的isIntersecting属性判断元素是否满足进入视口的阈值,比手动判断intersectionRatio更可靠
完整可用代码
const App = () =>{ const sectionRef = useRef(null); // 动画触发锁:标记是否已经播放入场动画 const hasAnimatedRef = useRef(false); const headlineFirst = ".animate-this-div"; const intersection = useIntersection(sectionRef, { root: null, rootMargin: "0px", threshold: 0.4 }); const fadeIn = () => { gsap.to(headlineFirst, 1, { opacity: 1, y: 11, }); }; // 替换原有三元判断的核心逻辑 // 锁未触发时,才检查交叉观察器状态 if (!hasAnimatedRef.current) { // 元素首次达到进入视口的阈值 if (intersection?.isIntersecting) { // 先把锁标记为已触发,后续所有逻辑都会永久跳过动画执行 hasAnimatedRef.current = true; // 执行一次完整的入场动画 fadeIn(); } } return ( <div ref={sectionRef} className="some-div"> <div className="animate-this-div"></div> </div> ) }
实际运行效果
- 不需要写任何退出动画相关的逻辑,锁触发后不管元素是滑出视口还是再次滑回,都不会触发任何动画操作
- 彻底避免了重复调用GSAP导致的补间打断问题,入场动画只会在元素首次达到视口阈值时完整播放一次
内容的提问来源于stack exchange,提问作者Figo22
相关产品推荐
相关产品推荐

