function useActiveHeading()

in docs-sdk/markdown-renderer/src/Layout/TableOfContent/index.tsx [76:153]


function useActiveHeading(
  headings: ITocHeading[],
  scrollContainer: string | undefined
) {
  const [headingWithDomEl, setHeadingWithDomEl] = useState<
    Array<IHeadingWithDomEl>
  >([]);

  const [activeId, setActiveId] = useState<string>("");

  useEffect(() => {
    setHeadingWithDomEl(
      headings
        .map(heading => {
          const el = document.getElementById(heading.id);
          if (!el) return (null as unknown) as IHeadingWithDomEl;
          return { el, ...heading };
        })
        .filter(Boolean)
    );
  }, [headings]);

  useEffect(() => {
    let scrollContainerEl: Element | Window =
      (scrollContainer && document.querySelector(scrollContainer)) ||
      getScrollParent(headingWithDomEl[0]?.el) ||
      window;

    if (
      scrollContainerEl === document.body ||
      scrollContainerEl === document.documentElement
    ) {
      scrollContainerEl = window;
    }

    const handle = () => {
      if (!headingWithDomEl.length) return;
      const scrollParent = scrollContainerEl;
      const found = headingWithDomEl.find((heaidng, idx) => {
        // 如果第一个heading都还没有滚动过去,那么高亮第一个heading
        if (idx === 0 && !isHeadingScrolled(heaidng.el, scrollParent))
          return true;
        const nextHeading = headingWithDomEl[idx + 1];
        if (nextHeading) {
          return (
            isHeadingScrolled(heaidng.el, scrollParent) &&
            !isHeadingScrolled(nextHeading.el, scrollParent)
          );
        }
        return isHeadingScrolled(heaidng.el, scrollParent);
      });
      setActiveId(found?.id ?? "");
    };

    handle();

    scrollContainerEl.addEventListener("resize", handle);
    scrollContainerEl.addEventListener("scroll", handle);

    return () => {
      scrollContainerEl.removeEventListener("resize", handle);
      scrollContainerEl.removeEventListener("scroll", handle);
    };
  }, [headingWithDomEl, scrollContainer]);

  return activeId;

  function isHeadingScrolled(el: HTMLElement, scrollParent: Element | Window) {
    if ("document" in scrollParent) {
      return el.getBoundingClientRect().top <= 30;
    }
    return (
      el.getBoundingClientRect().top -
        scrollParent.getBoundingClientRect().top <=
      30
    );
  }
}