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

Next.js页面间锚点链接无法滚动的问题排查与解决问询

Next.js 页面间锚点链接滚动问题的解决方案

跨页面点击指向首页的锚点链接(比如/#apartments)时,页面加载后不会自动滚动到目标区块,但同一页面内点击或页面已缓存时正常,核心原因是loading.tsx显示期间,浏览器默认的锚点滚动逻辑未触发,固定延迟的方案又受网络波动影响。以下是几种可靠的解决方式:

方案1:用 MutationObserver 监听目标元素加载

通过监听DOM树的变化,一旦目标锚点元素渲染完成,立即执行滚动操作,完全不需要依赖固定延迟时间。

"use client";

import { usePathname } from "next/navigation";
import { useEffect } from "react";

export default function InterPageScroller() {
  const pathname = usePathname();

  useEffect(() => {
    const { hash } = location;
    if (!hash) return;

    const targetSelector = hash;
    let observer: MutationObserver | null = null;

    const checkAndScroll = () => {
      const elem = document.querySelector(targetSelector);
      if (elem) {
        elem.scrollIntoView({ behavior: "smooth" });
        observer?.disconnect();
      }
    };

    // 先立即检查一次
    checkAndScroll();

    // 元素不存在的话,监听整个body的DOM变化
    if (!document.querySelector(targetSelector)) {
      observer = new MutationObserver(() => checkAndScroll());
      observer.observe(document.body, { childList: true, subtree: true });
    }

    return () => observer?.disconnect();
  }, [pathname]);

  return null;
}

方案2:循环检查 + requestAnimationFrame

用requestAnimationFrame循环检测目标元素,直到找到后执行滚动并停止循环,比固定延迟更灵活,能适配不同加载速度。

"use client";

import { usePathname } from "next/navigation";
import { useEffect } from "react";

export default function InterPageScroller() {
  const pathname = usePathname();

  useEffect(() => {
    const { hash } = location;
    if (!hash) return;

    const targetSelector = hash;
    let rafId: number | null = null;

    const checkElement = () => {
      const elem = document.querySelector(targetSelector);
      if (elem) {
        elem.scrollIntoView({ behavior: "smooth" });
        rafId && cancelAnimationFrame(rafId);
      } else {
        rafId = requestAnimationFrame(checkElement);
      }
    };

    rafId = requestAnimationFrame(checkElement);

    return () => rafId && cancelAnimationFrame(rafId);
  }, [pathname]);

  return null;
}

方案3:结合首页动态内容的加载状态

如果首页的锚点区块包含服务器组件或异步加载的内容(比如图片、动态数据),可以在首页的客户端组件中监听load事件,确保所有资源加载完成后再滚动:

先创建一个客户端组件:

"use client";

import { useEffect } from "react";

export default function HomeScrollHandler() {
  useEffect(() => {
    const { hash } = location;
    if (!hash) return;

    const scrollToTarget = () => {
      const elem = document.querySelector(hash);
      elem && elem.scrollIntoView({ behavior: "smooth" });
    };

    // 监听所有资源加载完成
    window.addEventListener("load", scrollToTarget);
    return () => window.removeEventListener("load", scrollToTarget);
  }, []);

  return null;
}

然后在首页page.tsx中引入:

import HomeScrollHandler from "./home-scroll-handler";

export default function HomePage() {
  return (
    <>
      <HomeScrollHandler />
      {/* 首页内容,包括#apartments、#reviews等锚点区块 */}
    </>
  );
}

额外优化:手动控制导航跳转逻辑

直接修改导航链接的点击事件,手动处理路由跳转和后续滚动,逻辑更可控:

"use client";

import { useRouter } from "next/navigation";
import { ReactElement } from "react";

type NavLinkType = {
  href: string;
  label: string;
  icon?: ReactElement;
};

const HEADER_NAV_LINKS: NavLinkType[] = [
  { href: "/#apartments", label: "Apartments" },
  { href: "/#reviews", label: "Reviews" },
  { href: "/#contact-us", label: "Contact us" },
] as const;

export default function Header() {
  const router = useRouter();

  const handleNavClick = (e: React.MouseEvent, href: string) => {
    e.preventDefault();
    const [path, hash] = href.split("#");
    
    // 先跳转到目标页面
    router.push(path);

    // 循环检查元素并滚动
    const checkAndScroll = () => {
      if (!hash) return;
      const elem = document.getElementById(hash);
      if (elem) {
        elem.scrollIntoView({ behavior: "smooth" });
        window.removeEventListener("popstate", checkAndScroll);
      } else {
        requestAnimationFrame(checkAndScroll);
      }
    };

    window.addEventListener("popstate", checkAndScroll);
    requestAnimationFrame(checkAndScroll);
  };

  return (
    <nav className="hidden sm:block">
      <ul className="flex gap-6">
        {HEADER_NAV_LINKS.map(({ href, label }) => (
          <li key={label}>
            <a
              href={href}
              className="hover:underline"
              onClick={(e) => handleNavClick(e, href)}
            >
              {label}
            </a>
          </li>
        ))}
      </ul>
    </nav>
  );
}

内容的提问来源于stack exchange,提问作者netrolite

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 07:00:20