设置<header>为sticky时useDetectScroll触发无限循环的解决方法
React中使用useDetectScroll配合sticky header出现无限滚动循环的问题解决
问题原因
当你把header设为position: sticky时,滚动触发scrollDir状态变化,导致“Hide on scroll”的div显示/隐藏——这会直接改变header的总高度。页面滚动高度基于文档总高度计算,header高度变化会间接引发文档高度变动,进而触发滚动位置的微小调整;而useDetectScroll钩子会监听这个滚动变化,再次更新scrollDir,如此反复形成无限循环,最终导致元素闪烁。
解决方案
方案1:固定Header高度,避免高度变化影响滚动
给header设置固定高度(或min-height),确保无论可隐藏元素显示与否,header高度都保持不变,打断“高度变化→滚动调整→状态更新”的循环。
修改后的代码示例:
"use client"; import clsx from "clsx"; import useDetectScroll, { Direction } from "@smakss/react-scroll-direction"; const Page = () => { const { scrollDir } = useDetectScroll(); return ( <> {/* 给header设置固定高度,内部元素高度也固定 */} <header className="bg-white w-full sticky top-0 h-[120px] overflow-hidden"> <div className="container mx-auto flex items-center justify-between bg-red-500 h-[60px]"> Content </div> <div className={clsx( scrollDir === Direction.Still || scrollDir === Direction.Up ? "block" : "hidden", "container mx-auto bg-blue-500 flex items-center h-[60px]" )} > Hide on scroll </div> </header> <div className="h-[2048px] container mx-auto">Page content</div> </> ); }; export default Page;
也可以将可隐藏元素移出header,用单独的sticky容器包裹:
"use client"; import clsx from "clsx"; import useDetectScroll, { Direction } from "@smakss/react-scroll-direction"; const Page = () => { const { scrollDir } = useDetectScroll(); return ( <> {/* 主header保持原有sticky设置 */} <header className="bg-white w-full sticky top-0"> <div className="container mx-auto flex items-center justify-between bg-red-500"> Content </div> </header> {/* 可隐藏元素单独做sticky,紧跟主header */} <div className="sticky top-[60px]"> <div className={clsx( scrollDir === Direction.Still || scrollDir === Direction.Up ? "block" : "hidden", "container mx-auto bg-blue-500 flex items-center" )} > Hide on scroll </div> </div> <div className="h-[2048px] container mx-auto">Page content</div> </> ); }; export default Page;
方案2:调整useDetectScroll配置,增加防抖或阈值
该库支持配置防抖时间和滚动阈值,过滤掉微小的滚动变化,避免触发不必要的状态更新。
修改useDetectScroll的调用:
const { scrollDir } = useDetectScroll({ debounceTime: 100, // 防抖时间,单位毫秒 threshold: 10, // 滚动超过10px才触发状态更新 });
只有当滚动幅度足够大、且停止滚动一段时间后,才会更新scrollDir,避免header高度变化导致的微小滚动触发循环。
方案3:自定义滚动监听逻辑,手动控制状态
如果上述方案不满足需求,可以自己实现滚动监听,加入防抖和忽略微小滚动的逻辑,完全控制状态更新时机:
"use client"; import clsx from "clsx"; import { useState, useEffect } from "react"; enum Direction { Up = "Up", Down = "Down", Still = "Still", } const Page = () => { const [scrollDir, setScrollDir] = useState<Direction>(Direction.Still); let lastScrollY = 0; let timeoutId: NodeJS.Timeout | null = null; useEffect(() => { const handleScroll = () => { const currentScrollY = window.scrollY; // 忽略小于10px的滚动变化 if (Math.abs(currentScrollY - lastScrollY) < 10) return; if (timeoutId) clearTimeout(timeoutId); // 防抖:停止滚动100ms后再更新状态 timeoutId = setTimeout(() => { setScrollDir( currentScrollY > lastScrollY ? Direction.Down : currentScrollY < lastScrollY ? Direction.Up : Direction.Still ); lastScrollY = currentScrollY; }, 100); }; window.addEventListener("scroll", handleScroll); return () => { window.removeEventListener("scroll", handleScroll); if (timeoutId) clearTimeout(timeoutId); }; }, []); return ( <> <header className="bg-white w-full sticky top-0"> <div className="container mx-auto flex items-center justify-between bg-red-500"> Content </div> <div className={clsx( scrollDir === Direction.Still || scrollDir === Direction.Up ? "block" : "hidden", "container mx-auto bg-blue-500 flex items-center" )} > Hide on scroll </div> </header> <div className="h-[2048px] container mx-auto">Page content</div> </> ); }; export default Page;
内容的提问来源于stack exchange,提问作者Dave
相关产品推荐
相关产品推荐

