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'
});
};
注意事项
-
服务器端渲染(SSR) :在 SSR 环境中(如 Next.js),
window
对象在服务器端不可用,需要做条件判断:jsxuseEffect(() => { if (typeof window !== 'undefined') { // 客户端代码 } }, []);
-
TypeScript 类型:如果使用 TypeScript,需要为 ref 指定正确的类型:
tsxconst sectionRef = useRef<HTMLDivElement>(null);
-
浏览器兼容性 :
scrollIntoView
的smooth
行为在旧浏览器中可能不支持,可以考虑使用 polyfill 或动画库。
这些方法提供了从简单到高级的 React 锚点实现方案,你可以根据项目需求选择最适合的方式。