Next.js如何正确应用全局脚本 解决滚动淡入动画仅首屏生效问题
问题根因
- 你的
appearOnScroll.js是立即执行函数(IIFE),仅在页面首次加载时执行1次,Next.js走客户端路由切换页面不会触发全页重载,脚本不会重新执行 - 脚本执行时只会查询首次渲染的
.fade-in类元素,页面切换后新渲染的元素不会被加入观测队列 - 你对进入视口的元素调用了
unobserve取消观测,路由返回后原元素的appear类会随组件销毁重置,但不会被再次观测触发动画 - 补充:现有脚本存在笔误,观测元素的方法应为
observe(),你写的observer()是实例对象名,调用会报错
更优实现方案(推荐)
放弃全局脚本的实现方式,改用React组件化方式实现,完全适配Next.js客户端路由逻辑,无内存泄漏问题:
- 先实现通用的淡入包装组件
// components/FadeInItem.js import { useRef, useEffect } from 'react' export default function FadeInItem({ children, threshold = 1 }) { const elRef = useRef(null) useEffect(() => { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('appear') observer.unobserve(entry.target) } }) }, { threshold }) if (elRef.current) { observer.observe(elRef.current) } // 组件卸载时自动取消观测,避免内存泄漏 return () => { if (elRef.current) { observer.unobserve(elRef.current) } } }, [threshold]) return <div ref={elRef} className="fade-in">{children}</div> }
- 替换现有所有直接加
fade-in类的元素,用FadeInItem包裹即可:
// 原来的写法 <div className="fade-in">内容</div> // 替换为 <FadeInItem>内容</FadeInItem>
- 保留原有CSS即可:
.fade-in { opacity: 0; transition: opacity 0.4s ease-in-out; } .fade-in.appear { opacity: 1; }
现有代码快速修复方案(不改动现有类名绑定)
如果不想批量修改现有元素绑定,可直接修改FadeIn组件,监听Next.js路由变化每次重新执行观测逻辑:
- 删掉
appearOnScroll.js外部脚本,把逻辑移到FadeIn组件内部:
// components/FadeIn.js import { useEffect } from 'react' import { useRouter } from 'next/router' export default function FadeIn() { const router = useRouter() const initFadeObserver = () => { const faders = document.querySelectorAll('.fade-in'); const faderOptions = { threshold: 1 }; const appearOnScroll = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (!entry.isIntersecting) return entry.target.classList.add('appear') observer.unobserve(entry.target) }) }, faderOptions) // 修正原脚本的方法名笔误 faders.forEach(fader => appearOnScroll.observe(fader)) } useEffect(() => { // 首次加载执行 initFadeObserver() // 每次路由切换完成后重新执行 router.events.on('routeChangeComplete', initFadeObserver) // 组件卸载移除监听 return () => { router.events.off('routeChangeComplete', initFadeObserver) } }, [router.events]) return null }
- 删掉原来的Script引入和Portal逻辑,直接在_app.js里保留
<FadeIn />引入即可。
内容的提问来源于stack exchange,提问作者dylzee
相关产品推荐
相关产品推荐

