React实现页面滚动时侧边栏目录自动高亮当前标题的方法咨询
实现滚动时自动高亮目录项的方案
结合你的现有代码,我们可以通过滚动监听+位置计算+状态管理来实现这个功能,以下是具体修改步骤:
1. 重构高亮逻辑为React状态管理
把原本直接操作DOM的高亮逻辑改成用React状态控制,避免DOM操作和React状态不一致的问题:
export function TableOfContents() { const ref = React.useRef() const headings = useTocHighlight(ref) // 新增状态:跟踪当前激活的标题索引 const [activeIndex, setActiveIndex] = React.useState(null) const handleClickOnTopic = (e, index) => { e.preventDefault() setActiveIndex(index) // 点击时更新激活状态 const nav = document.querySelector('.header') const height = nav.getBoundingClientRect().height - 50 const target = e.currentTarget const splitted = target.href.split('/') const lastElement2 = splitted.pop() const topicId = document.querySelector(lastElement2) window.scrollTo(0, topicId.offsetTop - height) } // ...后续代码 }
然后在渲染<a>标签时,通过状态控制activeee类:
<a id={`heading${i}`} onClick={(e) => handleClickOnTopic(e, i)} href={heading.url} className={activeIndex === i ? 'activeee' : ''} > {heading.text} </a>
2. 添加滚动监听与位置计算逻辑
使用useEffect监听页面滚动,计算当前视口中的标题,自动更新激活状态:
React.useEffect(() => { // 防抖函数,避免滚动时频繁计算 let timeoutId const handleScroll = () => { clearTimeout(timeoutId) timeoutId = setTimeout(() => { const nav = document.querySelector('.header') const navHeight = nav.getBoundingClientRect().height - 50 // 获取页面中所有H2、H3元素(需确保这些元素的ID和TOC的href对应) const pageHeadings = document.querySelectorAll('h2, h3') if (!pageHeadings.length) return // 遍历标题,找到当前处于视口内的第一个标题 let currentActiveIndex = null pageHeadings.forEach((heading) => { const rect = heading.getBoundingClientRect() // 判断标题是否进入导航下方的有效区域(距离顶部navHeight的位置) if (rect.top <= navHeight + 20 && rect.bottom >= navHeight) { // 找到对应的TOC索引 const matchingTocIndex = headings.findIndex( tocItem => tocItem.url.includes(`#${heading.id}`) ) if (matchingTocIndex !== -1) { currentActiveIndex = matchingTocIndex } } }) setActiveIndex(currentActiveIndex) }, 100) // 100ms防抖,可根据需求调整 } // 添加滚动监听 window.addEventListener('scroll', handleScroll) // 组件卸载时移除监听 return () => { window.removeEventListener('scroll', handleScroll) clearTimeout(timeoutId) } }, [headings]) // 依赖headings,确保目录更新时重新绑定逻辑
3. 关键注意事项
- 确保页面中的H2/H3元素都有唯一ID,且TOC的
heading.url包含对应的#id(比如#section-1),这样才能匹配到对应的目录项。 - 防抖时间可根据体验调整,100ms左右既能保证性能又不会有明显延迟。
- 若你的
useTocHighlight钩子已经能正确获取H2/H3的信息,那上述逻辑可以直接对接;如果钩子返回的heading.url格式不对,需要调整matchingTocIndex里的匹配逻辑。
最终完整代码示例
export function TableOfContents() { const ref = React.useRef() const headings = useTocHighlight(ref) const [activeIndex, setActiveIndex] = React.useState(null) const handleClickOnTopic = (e, index) => { e.preventDefault() setActiveIndex(index) const nav = document.querySelector('.header') const height = nav.getBoundingClientRect().height - 50 const target = e.currentTarget const splitted = target.href.split('/') const lastElement2 = splitted.pop() const topicId = document.querySelector(lastElement2) window.scrollTo(0, topicId.offsetTop - height) } React.useEffect(() => { let timeoutId const handleScroll = () => { clearTimeout(timeoutId) timeoutId = setTimeout(() => { const nav = document.querySelector('.header') const navHeight = nav.getBoundingClientRect().height - 50 const pageHeadings = document.querySelectorAll('h2, h3') if (!pageHeadings.length) return let currentActiveIndex = null pageHeadings.forEach((heading) => { const rect = heading.getBoundingClientRect() if (rect.top <= navHeight + 20 && rect.bottom >= navHeight) { const matchingTocIndex = headings.findIndex( tocItem => tocItem.url.includes(`#${heading.id}`) ) if (matchingTocIndex !== -1) { currentActiveIndex = matchingTocIndex } } }) setActiveIndex(currentActiveIndex) }, 100) } window.addEventListener('scroll', handleScroll) return () => { window.removeEventListener('scroll', handleScroll) clearTimeout(timeoutId) } }, [headings]) if (!headings.length) return null return ( <TocContainer ref={ref}> <h4>On this page</h4> <ul> {headings.map((heading, i) => heading.url ? ( <li key={i} data-depth={heading.depth}> <a id={`heading${i}`} onClick={(e) => handleClickOnTopic(e, i)} href={heading.url} className={activeIndex === i ? 'activeee' : ''} > {heading.text} </a> </li> ) : null, )} </ul> </TocContainer> ) }
内容的提问来源于stack exchange,提问作者Guru Pawar
相关产品推荐
相关产品推荐

