这是问题的简短演示:
我正在使用 IntersectionObserver 制作响应式滚动间谍目录导航栏。正如您所看到的,每当我刷新页面时,IntersectionObserver 就会中断。但是,如果我单击不同的页面然后返回该页面,它会再次起作用。
这是我的交叉口观察者代码:
import { useEffect, useRef } from "react";
const useIntersectionObserver = (setActiveId, toc) => {
console.log("intersectionObserver")
const headingElementsRef = useRef({});
useEffect(() => {
console.log("useEffect");
// callback starts
const callback = (headings) => {
console.log("callback")
headingElementsRef.current = headings.reduce((map, headingElement) => {
map[headingElement.target.id] = headingElement;
return map;
}, headingElementsRef.current);
const visibleHeadings = [];
console.log("useref", headingElementsRef.current)
Object.keys(headingElementsRef.current).forEach((key) => {
const headingElement = headingElementsRef.current[key];
if (headingElement.isIntersecting) visibleHeadings.push(headingElement);
});
console.log("visible headings", visibleHeadings);
const getIndexFromId = (id) =>
headingElements.findIndex((heading) => heading.id === id);
if (visibleHeadings.length === 1) {
setActiveId(visibleHeadings[0].target.id);
console.log("visibleHeading", visibleHeadings[0].target.id)
} else if (visibleHeadings.length > 1) {
const sortedVisibleHeadings = visibleHeadings.sort(
(a, b) => getIndexFromId(a.target.id) > getIndexFromId(b.target.id)
);
setActiveId(sortedVisibleHeadings[0].target.id);
console.log("sortedVisibleHeading", sortedVisibleHeadings[0].target.id)
}
};
//callback ends
const observer = new IntersectionObserver(callback, {
rootMargin: "-90px 0px -40% 0px",
});
const headingElements = Array.from(
document
.getElementById("content")
.querySelectorAll("h1, h2, h3, h4, h5, h6")
);
// const headingElements = Array.from(document.querySelectorAll("h1, h2, h3, h4, h5, h6"));
console.log("headingElements", headingElements)
headingElements.forEach((element) => observer.observe(element));
return () => {
observer.disconnect();
headingElementsRef.current = {};
};
}, [toc]);
// });
};
export default useIntersectionObserver;
在检查
headingElementsRef.current
控制台日志时,我发现页面刷新后我得到了这个:
dataflow-canonical-forms: IntersectionObserverEntry
boundingClientRect: DOMRectReadOnly {x: 0, y: 0, width: 0, height: 0, top: 0, …}
intersectionRatio: 0
intersectionRect: DOMRectReadOnly {x: 0, y: 0, width: 0, height: 0, top: 0, …}
isIntersecting: false
isVisible: false
rootBounds: null
target: h2#dataflow-canonical-forms.chakra-heading.css-6rvmc6
time: 5277.20000000298
请注意,boundingClientRect 始终为 0...这真的很奇怪。关于为什么会这样的任何想法吗?
到目前为止,我发现的唯一修复方法是从
useEffect
中删除依赖项数组,但从性能角度来看,我认为这不是非常理想的...
我认为 DOM Node 属性(例如boundingClientRect)的值仅在组件安装后才可用。这可以解释为什么它在软路由之间导航时有效,而在刷新时中断。
您需要使用
componentDidMount()
确保在 React 生命周期的正确阶段运行代码
请参阅此处了解更多详情
所以我遇到了同样的错误,我真的认为现有的答案根本没有抓住问题。
由于我们的实现不同,我的解决方案不会直接转换,但粗略的想法是使用 MutationObserver 来初始化回调。我还使用 useLayoutEffect 而不是 useEffect,但这实际上并没有改变错误的行为。
import { useLayoutEffect, useState, useRef } from "react"
export function useHeadingObserver() {
const observer = useRef<IntersectionObserver | undefined>()
const [activeId, setActiveId] = useState<string | null>(null)
useLayoutEffect(() => {
const initializeObserver = () => {
const headings = document.querySelectorAll("h2")
if (headings.length === 0) {
return
}
const handleObserver = (entries: IntersectionObserverEntry[]) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setActiveId(entry.target.id)
}
})
}
const handleScroll = () => {
if (headings.length === 0) return
// is at the top
if (window.scrollY === 0) {
setActiveId(headings[0].id)
}
// is at the bottom
if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
setActiveId(headings[headings.length - 1].id)
}
}
window.addEventListener("scroll", handleScroll)
observer.current = new IntersectionObserver(handleObserver, {
root: null,
rootMargin: "-0px 0px -40% 0px",
threshold: 1,
})
headings.forEach((heading) => {
observer.current?.observe(heading)
})
}
// Use MutationObserver to detect when headings are added to the DOM
const targetNode = document.body
const config = { childList: true, subtree: true }
const mutationObserver = new MutationObserver(() => {
initializeObserver() // Re-initialize the observer when the DOM changes the mutation observer stops this from breaking on page reload
})
mutationObserver.observe(targetNode, config)
return () => {
mutationObserver.disconnect()
observer.current?.disconnect()
}
}, [activeId])
return { activeId }
}