自定义useScrollPosition Hook检测window.pageYOffset异常问题求助
问题分析
当滚动位置在100px临界值附近时,微小的滚动动作都会让window.pageYOffset > 100的结果反复切换,再加上scroll事件本身触发频率极高,直接导致状态频繁更新,进而引发UI闪烁。另外原Hook未清理滚动监听,存在内存泄漏风险。
解决方案
方案1:添加滞后阈值(推荐)
设置两个不同的触发阈值,给状态切换留一个缓冲区间,彻底避免临界值附近的频繁切换:
const useScrollPosition = () => { const [scrollPosition, setScrollPosition] = useState(false); // 定义上下阈值:向下滚动超过120才隐藏,向上滚动低于80才显示 const SCROLL_DOWN_THRESHOLD = 120; const SCROLL_UP_THRESHOLD = 80; useEffect(() => { if (typeof window === 'undefined') return; const handleScroll = () => { const currentY = window.pageYOffset; setScrollPosition(prev => { // 向下滚动:当前位置超过下阈值,且之前未隐藏,才更新状态 if (currentY > SCROLL_DOWN_THRESHOLD && !prev) { return true; } // 向上滚动:当前位置低于上阈值,且之前已隐藏,才更新状态 if (currentY < SCROLL_UP_THRESHOLD && prev) { return false; } // 其他情况保持原状态,不触发组件更新 return prev; }); }; window.addEventListener('scroll', handleScroll); // 组件卸载时清理监听 return () => window.removeEventListener('scroll', handleScroll); }, []); return [scrollPosition]; }; export default useScrollPosition;
方案2:防抖处理
通过限制状态更新的频率,避免短时间内多次触发状态变更:
const useScrollPosition = () => { const [scrollPosition, setScrollPosition] = useState(false); useEffect(() => { if (typeof window === 'undefined') return; let timeoutId; const handleScroll = () => { // 清除之前的定时器,确保只在滚动停止后一段时间才更新状态 clearTimeout(timeoutId); timeoutId = setTimeout(() => { setScrollPosition(window.pageYOffset > 100); }, 100); // 100ms防抖时间,可根据需求调整 }; window.addEventListener('scroll', handleScroll); return () => { window.removeEventListener('scroll', handleScroll); clearTimeout(timeoutId); // 清理残留定时器 }; }, []); return [scrollPosition]; }; export default useScrollPosition;
额外优化建议
- 可以将Hook返回值改为对象,提高可读性:
return { isScrolled: scrollPosition };,使用时直接解构:const { isScrolled } = useScrollPosition(); - 若使用React 18+,可结合
useEffectEvent抽离滚动处理函数,进一步优化依赖逻辑,但上述方案已能满足稳定需求。
内容的提问来源于stack exchange,提问作者Ben Shekhtman
相关产品推荐
相关产品推荐

