React 手动实现页面锚点导航

React 手动实现页面锚点导航

在 React 中实现页面锚点导航(跳转到页面特定位置)有多种方法,以下是几种常见的手动实现方式:

方法一:使用原生 HTML 锚点

1. 基本实现

jsx 复制代码
function AnchorDemo() {
  return (
    <div>
      {/* 导航链接 */}
      <nav>
        <a href="#section1">Section 1</a>
        <a href="#section2">Section 2</a>
        <a href="#section3">Section 3</a>
      </nav>

      {/* 内容区块 */}
      <div id="section1" style={{ height: '500px', margin: '20px 0' }}>
        <h2>Section 1</h2>
      </div>
      
      <div id="section2" style={{ height: '500px', margin: '20px 0' }}>
        <h2>Section 2</h2>
      </div>
      
      <div id="section3" style={{ height: '500px', margin: '20px 0' }}>
        <h2>Section 3</h2>
      </div>
    </div>
  );
}

2. 注意事项

  • 这种方法最简单,但跳转时会有突然的"跳动"效果
  • URL 中会显示锚点部分(如 #section1

方法二:使用 React Refs + scrollIntoView

1. 完整实现

jsx 复制代码
import { useRef } from 'react';

function ScrollDemo() {
  const section1Ref = useRef(null);
  const section2Ref = useRef(null);
  const section3Ref = useRef(null);

  const scrollToSection = (elementRef) => {
    elementRef.current.scrollIntoView({
      behavior: 'smooth',
      block: 'start'
    });
  };

  return (
    <div>
      {/* 导航按钮 */}
      <nav>
        <button onClick={() => scrollToSection(section1Ref)}>Section 1</button>
        <button onClick={() => scrollToSection(section2Ref)}>Section 2</button>
        <button onClick={() => scrollToSection(section3Ref)}>Section 3</button>
      </nav>

      {/* 内容区块 */}
      <div ref={section1Ref} style={{ height: '500px', margin: '20px 0' }}>
        <h2>Section 1</h2>
      </div>
      
      <div ref={section2Ref} style={{ height: '500px', margin: '20px 0' }}>
        <h2>Section 2</h2>
      </div>
      
      <div ref={section3Ref} style={{ height: '500px', margin: '20px 0' }}>
        <h2>Section 3</h2>
      </div>
    </div>
  );
}

2. 优点

  • 平滑滚动效果(通过 behavior: 'smooth'
  • 不会修改 URL
  • 完全由 React 控制

方法三:结合 React Router 的锚点

如果使用 React Router,可以这样实现:

jsx 复制代码
import { useRef } from 'react';
import { useLocation, Link } from 'react-router-dom';

function RouterAnchorDemo() {
  const section1Ref = useRef(null);
  const section2Ref = useRef(null);
  const location = useLocation();

  // 监听 hash 变化
  useEffect(() => {
    if (location.hash === '#section1' && section1Ref.current) {
      section1Ref.current.scrollIntoView({ behavior: 'smooth' });
    } else if (location.hash === '#section2' && section2Ref.current) {
      section2Ref.current.scrollIntoView({ behavior: 'smooth' });
    }
  }, [location]);

  return (
    <div>
      {/* 使用 Link 而不是普通 a 标签 */}
      <nav>
        <Link to="#section1">Section 1</Link>
        <Link to="#section2">Section 2</Link>
      </nav>

      <div ref={section1Ref} id="section1" style={{ height: '500px' }}>
        <h2>Section 1</h2>
      </div>
      
      <div ref={section2Ref} id="section2" style={{ height: '500px' }}>
        <h2>Section 2</h2>
      </div>
    </div>
  );
}

方法四:自定义 Hook 封装

可以创建一个可复用的自定义 Hook:

jsx 复制代码
// useScrollTo.js
import { useRef, useCallback } from 'react';

export function useScrollTo() {
  const ref = useRef(null);
  
  const scrollTo = useCallback((options = {}) => {
    if (ref.current) {
      ref.current.scrollIntoView({
        behavior: 'smooth',
        block: 'start',
        ...options
      });
    }
  }, []);

  return [ref, scrollTo];
}

// 使用示例
function ScrollHookDemo() {
  const [section1Ref, scrollToSection1] = useScrollTo();
  const [section2Ref, scrollToSection2] = useScrollTo();

  return (
    <div>
      <nav>
        <button onClick={() => scrollToSection1()}>Section 1</button>
        <button onClick={() => scrollToSection2({ behavior: 'auto' })}>Section 2 (Instant)</button>
      </nav>

      <div ref={section1Ref} style={{ height: '500px' }}>
        <h2>Section 1</h2>
      </div>
      
      <div ref={section2Ref} style={{ height: '500px' }}>
        <h2>Section 2</h2>
      </div>
    </div>
  );
}

处理固定导航栏的偏移

如果有固定位置的导航栏,需要调整滚动位置:

jsx 复制代码
const scrollToSection = (elementRef) => {
  const navbarHeight = 60; // 你的导航栏高度
  const elementPosition = elementRef.current.getBoundingClientRect().top;
  const offsetPosition = elementPosition + window.pageYOffset - navbarHeight;

  window.scrollTo({
    top: offsetPosition,
    behavior: 'smooth'
  });
};

注意事项

  1. 服务器端渲染(SSR) :在 SSR 环境中(如 Next.js),window 对象在服务器端不可用,需要做条件判断:

    jsx 复制代码
    useEffect(() => {
      if (typeof window !== 'undefined') {
        // 客户端代码
      }
    }, []);
  2. TypeScript 类型:如果使用 TypeScript,需要为 ref 指定正确的类型:

    tsx 复制代码
    const sectionRef = useRef<HTMLDivElement>(null);
  3. 浏览器兼容性scrollIntoViewsmooth 行为在旧浏览器中可能不支持,可以考虑使用 polyfill 或动画库。

这些方法提供了从简单到高级的 React 锚点实现方案,你可以根据项目需求选择最适合的方式。

相关推荐
ZC跨境爬虫3 小时前
跟着 MDN 学 HTML day_9:(信件语义标记)
前端·css·笔记·ui·html
前端老石人3 小时前
HTML 字符引用完全指南
开发语言·前端·html
matlab_xiaowang4 小时前
Redux 入门:JavaScript 可预测状态管理库
开发语言·javascript·其他·ecmascript
幼儿园技术家4 小时前
前端如何设计权限系统(RBAC / ABAC)?
前端
前端摸鱼匠5 小时前
Vue 3 的v-bind合并行为:讲解v-bind与普通属性合并的规则
前端·javascript·vue.js·前端框架·ecmascript
REDcker6 小时前
浏览器端Web程序性能分析与优化实战 DevTools指标与工程清单
开发语言·前端·javascript·vue·ecmascript·php·js
donecoding7 小时前
一个 sudo 引发的血案:npm 全局包权限错乱彻底修复
前端·node.js·前端工程化
风骏时光牛马7 小时前
Raku正则匹配与数据批量处理实操案例
前端
nbwenren7 小时前
2026实测:Gemini 3 镜像站视觉能力实践——拍照原型图,一键生成 HTML+CSS 代码
前端·css·html
Lee川7 小时前
Prisma 实战指南:像搭积木一样设计古诗词数据库
前端·数据库·后端