React项目中如何实现一个简单的锚点目录定位

在这里插入图片描述

小册

这是我整理的学习资料,非常系统和完善,欢迎一起学习

  • 现代JavaScript高级小册

  • 深入浅出Dart

  • 现代TypeScript高级小册

  • linwu的算法笔记📒

前言

锚点目录定位功能在长页面和文档类网站中非常常见,它可以让用户快速定位到页面中的某个章节

  • 如何在React中实现锚点定位和平滑滚动
  • 目录自动高亮的实现思路
  • 处理顶部导航遮挡锚点的解决方案
  • 服务端渲染下的实现方案
  • 性能优化策略

实现基本锚点定位

首先,我们需要实现页面内基本的锚点定位功能。对于锚点定位来说,主要涉及这两个部分:

  1. 设置锚点,为页面中的某个组件添加id属性
  2. 点击链接,跳转到指定锚点处

20210106205503299.gif
例如:

// 锚点组件
function AnchorComponent() {return <h2 id="anchor">This is anchor</h2> 
}// 链接组件
function LinkComponent() {return (<a href="#anchor">Jump to Anchor</a> )
}

当我们点击Jump to Anchor这个链接时,页面会平滑滚动到AnchorComponent所在的位置。

使用useScrollIntoView自定义hook

React中实现锚点定位,最简单的方式就是使用useScrollIntoView这个自定义hook。

import { useScrollIntoView } from 'react-use';function App() {const anchorRef = useRef();  const scrollToAnchor = () => {useScrollIntoView(anchorRef);}return (<><a href="#anchor" onClick={scrollToAnchor}>Jump to Anchor  </a><h2 id="anchor" ref={anchorRef}>This is anchor</h2></>)
}

useScrollIntoView接受一个ref对象,当调用这个hook函数时,会自动滚动页面,使得ref对象在可视区域内。

原生scrollIntoView方法

useScrollIntoView内部其实就是使用了原生的scrollIntoView方法,所以我们也可以直接调用:

function App() {const anchorRef = useRef();const scrollToAnchor = () => {anchorRef.current.scrollIntoView({behavior: 'smooth',block: 'start'})};return (<>  <a href="#anchor" onClick={scrollToAnchor}>Jump to Anchor</a><h2 id="anchor" ref={anchorRef}>This is anchor</h2> </>)}

scrollIntoView可以让元素的父容器自动滚动,将这个元素滚动到可见区域。behavior:'smooth’可以启用平滑滚动效果。

锚点定位和目录联动

很多时候,我们会在页面中实现一个目录导航,可以快速定位到各个章节。此时就需要实现锚点定位和目录的联动效果:

  • 点击目录时,自动滚动到对应的章节
  • 滚动页面时,自动高亮正在浏览的章节

目录导航组件

目录导航本身是一个静态组件,我们通过props传入章节数据:

function Nav({ chapters }) {return (<ul className=" chapters">{chapters.map(chapter => (<li key={chapter.id}><a href={'#' + chapter.id}>  {chapter.title}</a></li>))}</ul>)
}

锚点组件

然后在页面中的每一章使用Anchor组件包裹:

function Chapter({ chapter }) {return (<Anchor id={chapter.id}>  <h2>{chapter.title}</h2>{chapter.content}</Anchor>)
}function Anchor({ children, id }) {return (<div id={id}>{children}  </div>)
}

这样通过id属性建立章节内容和目录链接之间的关联。

处理点击事件

当点击目录链接时,需要滚动到对应的章节位置:

function App() {//...const scrollToChapter = (chapterId) => {const chapterEl = document.getElementById(chapterId);chapterEl.scrollIntoView({ behavior: 'smooth' });}return (<><Nav chapters={chapters}onLinkClick={(chapterId) => scrollToChapter(chapterId)} />{chapters.map(chapter => (<Chapter key={chapter.id}chapter={chapter}/>))}</>)
}

给Nav组件传一个onLinkClick回调,当点击链接时,通过chapterId获取到元素,并滚动到可视区域,实现平滑跳转。

自动高亮

实现自动高亮也很简单,通过监听滚动事件,计算章节元素的偏移量,判断哪个章节在可视区域内,并更新active状态:

function App() {const [activeChapter, setActiveChapter] = useState();useEffect(() => {const handleScroll = () => {chapters.forEach(chapter => {const element = document.getElementById(chapter.id);// 获取元素在可视区域中的位置const rect = element.getBoundingClientRect();  // 判断是否在可视区域内 if (rect.top >= 0 && rect.bottom <= window.innerHeight) {setActiveChapter(chapter.id);}})}window.addEventListener('scroll', handleScroll);return () => {window.removeEventListener('scroll', handleScroll);}}, []);return (<><Navchapters={chapters}activeChapter={activeChapter}/></>)}

通过getBoundingClientRect可以得到元素相对于视窗的位置信息,根据位置判断是否在可见区域内,如果是就更新activeChapter状态,从而触发目录的高亮效果。

问题解析

遮挡问题

有时锚点会被固定的Header遮挡,此时滚动会定位到元素上方,用户看不到锚点对应的内容。

常见的解决方案是:

  1. 设置锚点元素margin-top
#anchor {margin-top: 80px; /* header高度 */
}

直接设置一个和Header高度相同的margin,来防止遮挡。

  1. 在滚动方法中加入offset
// scroll offset
const scrollOffset = -80; chapterEl.scrollIntoView({offsetTop: scrollOffset
})

给scrollIntoView传入一个顶部偏移量,这样也可以跳过Header的遮挡。

响应式问题

在响应式场景下,目录的遮挡问题会更复杂。我们需要区分不同断点下,计算匹配的offset。

可以通过MatchMedia Hook获取当前的断点:

import { useMediaQuery } from 'react-responsive';function App() {const isMobile = useMediaQuery({ maxWidth: 767 });const isTablet = useMediaQuery({ minWidth: 768, maxWidth: 1023 });const isDesktop = useMediaQuery({ minWidth: 1024 });let scrollOffset = 0;if (isMobile) {scrollOffset = 46; } else if (isTablet) {  scrollOffset = 60;} else if (isDesktop) {scrollOffset = 80;}const scrollToChapter = (chapterId) => {const chapterEl = document.getElementById(chapterId);chapterEl.scrollIntoView({offsetTop: scrollOffset  })}//...}

根据不同断点,动态计算滚动偏移量,这样可以适配所有情况。

性能优化

使用节流

滚动事件会高频触发,直接在滚动回调中计算章节位置会造成性能问题。

我们可以使用Lodash的throttle函数进行节流:

import throttle from 'lodash.throttle';const handleScroll = throttle(() => {// 计算章节位置
}, 100);

这样可以限制滚动事件最多每100ms触发一次。

IntersectionObserver

使用IntersectionObserver提供的异步回调,只在章节进入或者离开可视区域时才执行位置计算:

import { useRef, useEffect } from 'react';function App() {const chaptersRef = useRef({});useEffect(() => {const observer = new IntersectionObserver((entries) => {// 章节进入或者离开可视区域时更新});chapters.forEach(chapter => {observer.observe(document.getElementById(chapter.id)  );})}, []);} 

这种懒加载式的方式可以大幅减少无效的位置计算。

SSR支持

在Next.js等SSR场景下,客户端脚本会延后加载,页面初次渲染时目录联动会失效。

getInitialProps注水

可以在getInitialProps中提前计算目录数据,注入到页面中:

Home.getInitialProps = async () => {const chapters = await fetchChapters();const mappedChapters = chapters.map(chapter => {return {...chapter,highlighted: isChapterHighlighted(chapter) }});return {chapters: mappedChapters};};

hydrate处理

客户端脚本加载后,需要调用ReactDOM.hydrate而不是render方法,进行数据的补充填充,避免目录状态丢失。

import { useEffect } from 'react';function App({ chapters }) {useEffect(() => {ReactDOM.hydrate(<App chapters={chapters} />,  document.getElementById('root'));}, []);}

服务端渲染的实现方案

image.png

在使用了服务端渲染(SSR)的框架如Next.js等情况下,实现锚点定位和目录联动也会有一些不同。

主要区别在于:

  • 服务端和客户端环境不统一
  • 脚本加载时间差

这会导致一些状态错位的问题。

问题复现

假设我们有下面的目录和内容结构:

function Nav({ chapters }) {return (<ul>{chapters.map(ch => (<li><a href={'#' + ch.id}>{ch.title}</a></li>))}</ul>)
}function Chapter({ chapter }) {const ref = useRef();// 占位组件return <div ref={ref}>{chapter.content}</div> 
}function App() {const chapters = [{ id: 'chapter-1', title: 'Chapter 1' },{ id: 'chapter-2', title: 'Chapter 2' },];return (<><Nav chapters={chapters} /><Chapter chapter={chapters[0]} /><Chapter chapter={chapters[1]} /></>)}

非SSR环境下,点击链接和滚动都可以正常工作。

但是在Next.js的SSR环境下就会有问题:

点击目录链接时,页面不会滚动。

这是因为在服务端,我们无法获取组件的ref,所以锚点元素不存在,自然无法定位。

滚动页面时,目录高亮也失效。

服务端渲染的静态HTML中,并没有绑定滚动事件,所以无法自动高亮。

预取数据

首先,我们需要解决点击目录链接的问题。

既然服务端无法获取组件ref,那就需要在客户端去获取元素位置。

这里有两个方法:

  1. 组件挂载后主动缓存元素位置
// Chapter组件useEffect(() => {// 缓存位置数据cacheElementPosition(chapter.id, ref.current); 
}, []);// Utilsconst elementPositions = {};function cacheElementPosition(id, element) {const rect = element.getBoundingClientRect();elementPositions[id] = {left: rect.left,top: rect.top,}
}
  1. 点击时实时获取元素位置
// handle link clickconst scrollToChapter = (chapterId) => {const element = document.getElementById(chapterId);const rect = element.getBoundingClientRect();window.scrollTo({top: rect.top,behavior: 'smooth'})}

无论哪种方法,都需要在组件挂载后获取元素的位置信息。

这样我们就可以在点击目录链接时,正确滚动到对应的章节位置了。

数据注水

但是点击目录只解决了一半问题,滚动高亮还需要解决。

这里就需要用到数据注水的技术。

简单来说就是:

  • 在服务端渲染时,读取路由参数,提前计算高亮状态
  • 将高亮数据注入到响应中
  • 客户端拿到注水的数据后渲染,不会出现高亮错位

实现步骤:

1.服务端获取参数和数据

// 在getServerSideProps中export async function getServerSideProps(context) {const { hashtag } = context.query;const chapters = await fetchChapters();const highlightedChapter = chapters.find(ch => ch.id === hashtag);return {props: {chapters,highlightedChapter  }}}

2.客户端读取props

function Nav({ chapters, highlightedChapter }) {return (<ul>{chapters.map(ch => (<li className={ch.id === highlightedChapter?.id ? 'highlighted' : ''}></li>))}</

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/142892.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GLTF编辑器也可以转换GLB模型

1、GLB模型介绍 GLB&#xff08;GLTF Binary&#xff09;是一种用于表示三维模型和场景的文件格式。GLTF是"GL Transmission Format"的缩写&#xff0c;是一种开放的、跨平台的标准&#xff0c;旨在在各种3D图形应用程序和引擎之间进行交换和共享。 GLB文件是GLTF文件…

Java之线程的详细解析一

实现多线程 简单了解多线程【理解】 是指从软件或者硬件上实现多个线程并发执行的技术。 具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程&#xff0c;提升性能。 并发和并行【理解】 并行&#xff1a;在同一时刻&#xff0c;有多个指令在多个CPU上同时执行…

【excel密码】如何给excel设置带有密码的只读模式

大家提起只读模式&#xff0c;应该都不会联想到密码&#xff0c;想起excel密码可能会想到打开密码或者工作表保护。今天给大家分享如何设置带有密码的只读模式。 打开excel文件&#xff0c;将文件进行【另存为】设置&#xff0c;然后停留在保存路径的界面中&#xff0c;我们点…

SourceTree 账号或者密码输入错误 Incorrect username or password ( access token )解决办法

修改来修改去一直解决不了&#xff0c;那就试试查看一下源文件记录的账号密码吧&#xff01;

谷器数据参加世界制造业大会及数字化转型高峰论坛

9月20日至24日&#xff0c;由工业和信息化部、科技部、商务部、国务院国资委、中国工程院、安徽省人民政府等单位组织共同主办的2023世界制造业大会在合肥市滨湖国际会展中心盛大举行。谷器数据受邀出席&#xff0c;并同期参加”数字化转型高峰论坛”&#xff0c;与国家工信部相…

自定义热加载:如何不停机实现核心代码更新

文章目录 1. 常见的几种实现代码热更新的几种方式对于开发环境我们可以使用部署环境1. 使用 Arthas 的 redefine 命令来加载新的 class 文件2. 利用 URLClassLoader 动态加载3. 通过Java的Instrumentation API 也是可以实现的 2. 实现1. ClassScanner扫描目录和加载类2. 定时任…

十六,镜面IBL--预滤波环境贴图

又到了开心的公式时刻了。 先看看渲染方程 现在关注第二部分&#xff0c;镜面反射。 其中 这里很棘手&#xff0c;与输入wi和输出w0都有关系&#xff0c;所以&#xff0c;再近似 其中第一部分&#xff0c;就是预滤波环境贴图&#xff0c;形式上与前面的辐照度图很相似&#…

离线环境harbor 搭建及使用

一 摘要 本文主要介绍harbor 的安装及使用。 二 环境信息及部署图 2.1 环境信息 名称版本备注操作系统centos7.9容器docker 23.0.1harbor2.7代理nginx待补充 2.2 架构图 说明&#xff1a; 1.harbor 核心服务里有个nginx &#xff0c;也可以用该nginx 做代理 2.proxy-ngin…

ISP图像信号处理——平场校正介绍以及C++实现

参考文章1&#xff1a;http://t.csdn.cn/h8TBy 参考文章2&#xff1a;http://t.csdn.cn/6nmsT 参考网址3&#xff1a;opencv平场定标 - CSDN文库 平场校正一般先用FPN(Fixed Pattern Noise)固定图像噪声校正,即暗场校正&#xff1b;再用PRNU(Photo Response Non Uniformity)…

自动化测试-友好的第三方库

目录 mock furl coverage deepdiff pandas jsonpath 自动化测试脚本开发中&#xff0c;总是会遇到各种数据处理&#xff0c;例如MOCK、URL处理、JSON数据处理、结果断言等&#xff0c;也会遇到所采用的测试框架不能满足当前需求&#xff0c;这些问题都需要我们自己动手解…

IP地址定位的特点

IP地址定位是一种广泛应用于网络领域的技术&#xff0c;它允许我们确定特定设备或用户在互联网上的位置。这项技术在很多方面都具有重要的特点&#xff0c;本文将深入探讨这些特点。 1.全球性覆盖&#xff1a; IP地址定位IP66_ip归属地在线查询_免费ip查询_ip精准定位平台具有全…

通信协议:Uart的Verilog实现(下)

4、UART接收器 UART接收器负责接收串行比特流&#xff0c;去除起始位和停止位&#xff0c;并以并行格式将数据保存到与主机数据总线相连的寄存器里。接收器无法获得发送时钟&#xff0c;因此尽管数据以标准比特率到达&#xff0c;但数据未必与接收主机内的时钟同步。同步问题可…

LaTex模板免费下载网站

LaTex模板免费下载网站 在进行文档排版时候&#xff0c;有时需要对不同类型文章的格式进行编辑&#xff0c;本博文推荐一个免费下载LaTex模板的网站。 一、网站地址 链接: LaTex模板网址&#xff1a;http://www.latextemplates.com/ 二、模板类型 模板类型如图2和图3所示。…

Ctfshow web入门 phpCVE篇 web311-web315 详细题解 全

CTFshow phpCVE web311 CVE-2019-11043 PHP远程代码执行漏洞复现&#xff08;CVE-2019-11043&#xff09;【反弹shell成功】-腾讯云开发者社区-腾讯云 (tencent.com) 漏洞描述 CVE-2019-11043 是一个远程代码执行漏洞&#xff0c;使用某些特定配置的 Nginx PHP-FPM 的服务…

程序开发常用在线工具汇总

菜鸟工具# https://c.runoob.com/ 编码# ASCII码# https://www.habaijian.com/ 在线转换# https://www.107000.com/T-Ascii/http://www.ab126.com/goju/1711.html Base64# 在线转换# https://www.qqxiuzi.cn/bianma/base64.htmhttp://www.mxcz.net/tools/Unicode.aspx …

【Html】用CSS定义咖啡 - 咖啡配料展示

显示效果 代码 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>CodePen - For The Love Of Coffee</title><link rel"stylesheet" href"./style.css">&l…

Java中的IO流的缓冲流

不爱生姜不吃醋⭐️ 如果本文有什么错误的话欢迎在评论区中指正 与其明天开始&#xff0c;不如现在行动&#xff01; 文章目录 &#x1f334;IO流体系结构&#x1f334;缓冲流1.提高效率的原理2.缓冲流的类型3.字符缓冲流两个特有方法 &#x1f334;总结 &#x1f334;IO流体系…

民企再续“助学故事”,恒昌公益两所“云杉校园”如何聚木成林?

撰稿|多客 来源|贝多财经 “生物世界丰富多彩、五花八门、琳琅满目&#xff0c;可谓大千世界芸芸众生”……这是遵义市正安县安场镇光明完全小学图书馆收藏的一本名为《闯入生物世界》书中所写景象。 在这所学校&#xff0c;课外书籍按照年级及类别进行划分&#xff0c;如一…

微信小程序案例2-3:婚礼邀请函

文章目录 一、运行效果二、知识储备&#xff08;一&#xff09;导航栏设置1、导航栏的相关配置项2、利用导航栏组件2、在页面配置文件中对导航栏进行配置3、在全局配置文件中对导航栏进行配置 三、实现步骤 一、运行效果 “婚礼邀请函”微信小程序由4个页面组成&#xff0c;分别…

ABB DDC779BE02 3BHE006805R0002 控制主板模块

ABB DDC779BE02 3BHE006805R0002 控制主板模块用于自动化和控制系统中&#xff0c;它们可能具有以下一些常见特点和功能&#xff1a; 处理能力&#xff1a;ABB DDC779BE02 3BHE006805R0002 控制主板模块通常具有强大的处理能力&#xff0c;可以执行复杂的控制算法和逻辑。 多种…