You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

设置<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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 14:07:10