React组件挂载后DOM元素未加载,如何实现锚点滚动?
解决React组件初始加载时滚动到锚点的问题
核心问题在于:组件挂载时,ReactMarkdown尚未完成Markdown内容的DOM渲染,导致你通过document.getElementById无法找到目标元素。DOMContentLoaded事件不触发是因为React组件挂载本身就在该事件之后执行,此时页面根DOM已加载,但组件内部的动态渲染内容还未生成。
以下是几种可行的解决方案:
方案一:监听ReactMarkdown渲染完成事件
利用ReactMarkdown的onRenderComplete回调(v7+版本支持,旧版本可使用onComplete),统计所有Markdown块的渲染完成状态,待全部渲染完成后再执行滚动操作:
import React, { useEffect, useState, useRef } from 'react' import PropTypes from 'prop-types' import ReactMarkdown from 'react-markdown' import { useLocation } from 'react-router-dom' const Sections = ({ sections }) => { const location = useLocation() const [allMarkdownRendered, setAllMarkdownRendered] = useState(false) const renderedCountRef = useRef(0) const handleMarkdownRendered = () => { renderedCountRef.current += 1 if (renderedCountRef.current === sections.length) { setAllMarkdownRendered(true) } } useEffect(() => { const { hash } = location if (!hash) return const scrollToAnchor = () => { const element = document.getElementById(hash.replace('#', '')) if (element) { element.scrollIntoView({ behavior: 'smooth' }) } } if (allMarkdownRendered) { scrollToAnchor() } }, [location, allMarkdownRendered]) // 当sections更新时重置渲染计数 useEffect(() => { renderedCountRef.current = 0 setAllMarkdownRendered(false) }, [sections]) return ( <div className="Sections"> {sections.map((section) => ( <div key={section.title} id={section.title} className="Section__anchor"> <ReactMarkdown onRenderComplete={handleMarkdownRendered}> {section.content} </ReactMarkdown> </div> ))} </div> ) }
方案二:使用MutationObserver监听DOM变化
通过MutationObserver监听组件容器的DOM变化,当目标锚点元素出现时立即执行滚动,无需依赖ReactMarkdown的回调:
import React, { useEffect, useRef } from 'react' import PropTypes from 'prop-types' import ReactMarkdown from 'react-markdown' import { useLocation } from 'react-router-dom' const Sections = ({ sections }) => { const location = useLocation() const sectionsRef = useRef(null) useEffect(() => { const { hash } = location if (!hash) return const targetId = hash.replace('#', '') // 创建观察者实例 const observer = new MutationObserver(() => { const element = document.getElementById(targetId) if (element) { element.scrollIntoView({ behavior: 'smooth' }) observer.disconnect() // 找到元素后停止监听 } }) if (sectionsRef.current) { observer.observe(sectionsRef.current, { childList: true, subtree: true }) } // 初始检查:防止元素已提前渲染完成 const element = document.getElementById(targetId) if (element) { element.scrollIntoView({ behavior: 'smooth' }) observer.disconnect() } // 组件卸载时清理观察者 return () => observer.disconnect() }, [location]) return ( <div ref={sectionsRef} className="Sections"> {sections.map((section) => ( <div key={section.title} id={section.title} className="Section__anchor"> <ReactMarkdown> {section.content} </ReactMarkdown> </div> ))} </div> ) }
方案三:依赖sections数据加载完成(适用于异步场景)
如果sections是通过API异步获取的,只需将sections加入useEffect的依赖数组,确保数据加载完成且DOM渲染后再执行滚动:
import React, { useEffect } from 'react' import PropTypes from 'prop-types' import ReactMarkdown from 'react-markdown' import { useLocation } from 'react-router-dom' const Sections = ({ sections }) => { const location = useLocation() useEffect(() => { const { hash } = location if (!hash || sections.length === 0) return const element = document.getElementById(hash.replace('#', '')) if (element) { element.scrollIntoView({ behavior: 'smooth' }) } }, [location, sections]) return ( <div className="Sections"> {sections.map((section) => ( <div key={section.title} id={section.title} className="Section__anchor"> <ReactMarkdown> {section.content} </ReactMarkdown> </div> ))} </div> ) }
内容的提问来源于stack exchange,提问作者Doug
相关产品推荐
相关产品推荐

