React Scrollspy

PHOTO EMBED

Sun Nov 27 2022 14:29:05 GMT+0000 (Coordinated Universal Time)

Saved by @avivdaniel #javascript

import React, { useState, useEffect } from 'react';
import throttle from 'lodash/fp/throttle';

export interface useScrollSpyParams {
  activeSectionDefault?: string; //The key of the section that should be active by default
  offsetPx?: number;
  sectionElementRefs: { [key: string]: React.RefObject<HTMLElement> }; //The key of each element should be uniq!
  throttleMs?: number;
  scrollingElement?: React.RefObject<HTMLElement>;
}

export const useScrollSpy = ({
  activeSectionDefault = '',
  offsetPx = 0,
  scrollingElement,
  sectionElementRefs = {},
  throttleMs = 100,
}: useScrollSpyParams) => {
  const [activeSection, setActiveSection] = useState(activeSectionDefault);

  const scrollIntoView = (sectionKey: string) => {
    const sectionElement = sectionElementRefs[sectionKey].current;
    const scrollingContainer = scrollingElement?.current || window;
    if (sectionElement && scrollingContainer) {
      scrollingContainer.scrollTo({
        top: sectionElement.getBoundingClientRect().top + offsetPx - 60,
        behavior: 'smooth',
      });
    }
  };

  const handle = throttle(throttleMs, () => {
    let currentSectionId = activeSection;

    Object.entries(sectionElementRefs).forEach(([sectionKey, ref]) => {
      const section = ref.current;
      if (!section || !(section instanceof Element)) return;
      // GetBoundingClientRect returns values relative to viewport
      if (section.getBoundingClientRect().top + offsetPx < 0) {
        currentSectionId = sectionKey;
        return;
      }
      return;
    });
    setActiveSection(currentSectionId);
  });

  useEffect(() => {
    const scrollable = scrollingElement?.current ?? window;
    scrollable.addEventListener('scroll', handle);

    // Run initially
    handle();

    return () => {
      scrollable.removeEventListener('scroll', handle);
    };
  }, [sectionElementRefs, offsetPx, scrollingElement, handle]);
  return { activeSection, scrollIntoView };
};
content_copyCOPY