@ehubbell/scrollspy
Version:
A lightweight scrollspy library for React projects.
88 lines (87 loc) • 3.16 kB
JavaScript
import { jsx } from "react/jsx-runtime";
import { useRef, useState, useEffect } from "react";
const ScrollSpy = ({
navRef,
activeClass = "active",
dataAttribute = "scrollspy",
offsetTop = 0,
offsetBottom = 0,
throttle = 100,
updateHistory = true,
onUpdate,
debug = false,
children
}) => {
const containerRef = useRef(null);
const [navItems, setNavItems] = useState(null);
const activeElement = useRef("");
useEffect(() => {
var _a;
if (navRef) {
const items = (_a = navRef.current) == null ? void 0 : _a.querySelectorAll(`[data-${dataAttribute}]`);
setNavItems(items);
} else {
const items = document.querySelectorAll(`[data-${dataAttribute}]`);
setNavItems(items);
}
}, [navRef]);
useEffect(() => {
updateScrollSpy();
}, [navItems]);
useEffect(() => {
window.addEventListener("scroll", scrollEvent);
return () => window.removeEventListener("scroll", scrollEvent);
}, [containerRef, navItems]);
function scrollEvent() {
setTimeout(updateScrollSpy, throttle);
}
function checkVisibility(element) {
const id = element.id;
if (!id)
return null;
const rectInView = element.getBoundingClientRect();
const height = (containerRef == null ? void 0 : containerRef.current) ? containerRef == null ? void 0 : containerRef.current.offsetHeight : window.innerHeight;
const container = height;
const elementTop = rectInView.bottom;
const elementBottom = rectInView.top + height;
const computedTop = elementTop + offsetTop;
const computedBottom = elementBottom + offsetBottom;
if (debug)
console.log("checkVisibility: ", { id, container, elementTop, elementBottom, computedTop, computedBottom });
return container < computedBottom && container > computedTop;
}
function updateScrollSpy() {
const container = containerRef.current;
if (!(container && navItems))
return;
for (let i = 0; i < container.children.length; i++) {
const scrollElement = container.children.item(i);
const scrollElementVisible = checkVisibility(scrollElement);
if (scrollElementVisible) {
const scrollElementId = scrollElement.id;
if (activeElement.current === scrollElementId)
return;
const computedElementId = scrollElementId || activeElement.current;
navItems.forEach((element) => {
const attrId = element.getAttribute(`data-${dataAttribute}`);
if (attrId !== computedElementId && element.classList.contains(activeClass))
element.classList.remove(activeClass);
if (attrId === computedElementId && !element.classList.contains(activeClass)) {
element.classList.add(activeClass);
if (onUpdate)
onUpdate(computedElementId);
activeElement.current = computedElementId;
if (updateHistory)
window.history.replaceState({}, "", `#${scrollElementId}`);
}
});
break;
}
}
}
return /* @__PURE__ */ jsx("div", { ref: containerRef, children });
};
export {
ScrollSpy as default
};
//# sourceMappingURL=index.es.js.map