IntersectionObserver 在页面刷新时中断,但仅在页面刷新时中断

问题描述 投票:0回答:2

这是问题的简短演示:

enter image description here

我正在使用 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
中删除依赖项数组,但从性能角度来看,我认为这不是非常理想的...

javascript reactjs
2个回答
0
投票

我认为 DOM Node 属性(例如boundingClientRect)的值仅在组件安装后才可用。这可以解释为什么它在软路由之间导航时有效,而在刷新时中断。

您需要使用

componentDidMount()

确保在 React 生命周期的正确阶段运行代码

请参阅此处了解更多详情


0
投票

所以我遇到了同样的错误,我真的认为现有的答案根本没有抓住问题。

由于我们的实现不同,我的解决方案不会直接转换,但粗略的想法是使用 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 }
}
© www.soinside.com 2019 - 2024. All rights reserved.