react-hooks-global-scrollspy
Version:
React Hook for managing navigation by scroll position
95 lines (94 loc) • 3.8 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
import { useEffect, useState, useRef, useCallback } from "react";
import { throttle } from "../utils/throttle";
/**
* @param ScrollSpyParams Optional params
* @param ScrollSpyParams.offsetPx Distance from Y coordinate of the base (px)
* @param ScrollSpyParams.throttleMs Interval of update processing (ms)
* @returns [ activeElement, actions ] - Two return values are returned as an array
* @returns activeElement - DOM element activated based on scroll position (only one)
* @returns actions - Actions are used to control registered DOM elements
*/
export var useScrollSpy = function (_a) {
var _b = _a === void 0 ? {} : _a, _c = _b.offsetPx, offsetPx = _c === void 0 ? 0 : _c, _d = _b.throttleMs, throttleMs = _d === void 0 ? 50 : _d;
var _e = useState(null), activeElement = _e[0], setActiveElement = _e[1];
var elements = useRef({});
var registerElement = useCallback(function (_a) {
var _b;
var key = _a.key, element = _a.element;
var prevElements = elements.current;
elements.current = __assign(__assign({}, prevElements), (_b = {}, _b[key] = element, _b));
}, []);
var unregisterElement = useCallback(function (_a) {
var key = _a.key;
var deletedElements = __assign({}, elements.current);
delete deletedElements[key];
elements.current = deletedElements;
}, []);
var resetElements = useCallback(function () {
elements.current = {};
}, []);
var updateActiveElement = useCallback(function () {
var currentElements = elements.current;
var elementKeys = Object.keys(currentElements);
var activeKey = elementKeys.reduce(function (prevActive, key) {
var elementElement = currentElements[key].current;
if (!elementElement) {
return prevActive;
}
var elementPositionTop = elementElement.getBoundingClientRect().top;
var isPassed = elementPositionTop + offsetPx < 0;
if (!isPassed) {
return prevActive;
}
if (!prevActive.key) {
return {
key: key,
elementPositionTop: elementPositionTop,
};
}
var _a = prevActive.elementPositionTop, prevElementPositionTop = _a === void 0 ? 0 : _a;
var isHigherThanPrev = elementPositionTop >= prevElementPositionTop;
if (isHigherThanPrev) {
return {
key: key,
elementPositionTop: elementPositionTop,
};
}
else {
return prevActive;
}
}, {}).key;
if (!activeKey) {
setActiveElement(null);
return;
}
setActiveElement({
key: activeKey,
value: currentElements[activeKey],
});
}, [offsetPx]);
useEffect(function () {
var InvokeUpdate = throttle(updateActiveElement, throttleMs);
window.addEventListener("scroll", InvokeUpdate);
return function () {
window.removeEventListener("scroll", InvokeUpdate);
};
}, [throttleMs, updateActiveElement]);
var actions = {
registerElement: registerElement,
unregisterElement: unregisterElement,
resetElements: resetElements,
};
return [activeElement, actions];
};