@bongione/react-element-scroll-hook
Version:
A react hook to use the scroll information of an element
192 lines (159 loc) • 6.32 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _react = require("react");
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(n); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
// Edge has a bug where scrollHeight is 1px bigger than clientHeight when there's no scroll.
var isEdge = /Edge\/\d./i.test(typeof navigator !== 'undefined' ? navigator.userAgent : ''); // Small hook to use ResizeOberver if available. This fixes some issues when the component is resized.
// This needs a polyfill to work on all browsers. The polyfill is not included in order to keep the package light.
function useResizeObserver(ref, callback) {
(0, _react.useEffect)(function () {
if (typeof window !== "undefined" && window.ResizeObserver) {
var resizeObserver = new ResizeObserver(function (entries) {
callback(entries[0].contentRect);
});
if (ref.current) {
resizeObserver.observe(ref.current);
return function () {
resizeObserver.disconnect();
};
}
}
}, [ref]);
}
function throttle(func, wait) {
var a;
var context, args, result;
var timeout = null;
var previous = 0;
var later = function later() {
timeout = null;
result = func.apply(context, args || []);
if (!timeout) {
context = args = null;
}
};
return function () {
var now = Date.now();
var remaining = wait - (now - previous);
context = this;
args = Array.prototype.slice.call(arguments);
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) {
context = args = null;
}
} else if (!timeout) {
timeout = setTimeout(later, remaining);
}
return result;
};
}
function useScrollInfo() {
var _useState = (0, _react.useState)({
x: {},
y: {}
}),
_useState2 = _slicedToArray(_useState, 2),
scroll = _useState2[0],
setScroll = _useState2[1];
var ref = (0, _react.useRef)(null);
var previousScroll = (0, _react.useRef)(null);
useResizeObserver(ref, function () {
update();
});
var throttleTime = 50;
function update() {
var element = ref.current;
var maxY = element.scrollHeight - element.clientHeight;
var maxX = element.scrollWidth - element.clientWidth; // Edge has a bug where scrollHeight is 1px bigger than clientHeight when there's no scroll.
if (isEdge && maxY === 1 && element.scrollTop === 0) {
maxY = 0;
}
var percentageY = maxY !== 0 ? element.scrollTop / maxY : null;
var percentageX = maxX !== 0 ? element.scrollLeft / maxX : null;
var classNameY = "no-scroll-y";
if (percentageY === 0) {
classNameY = "scroll-top";
} else if (percentageY === 1) {
classNameY = "scroll-bottom";
} else if (percentageY) {
classNameY = "scroll-middle-y";
}
var classNameX = "no-scroll-x";
if (percentageX === 0) {
classNameX = "scroll-left";
} else if (percentageX === 1) {
classNameX = "scroll-right";
} else if (percentageX) {
classNameX = "scroll-middle-x";
}
var previous = previousScroll.current || {
x: {
className: "",
direction: 0,
percentage: 0,
total: 0,
value: 0
},
y: {
className: "",
direction: 0,
percentage: 0,
total: 0,
value: 0
}
};
var scrollInfo = {
x: {
percentage: percentageX,
value: element.scrollLeft,
total: maxX,
className: classNameX,
direction: previous ? Math.sign(element.scrollLeft - previous.x.value) : 0
},
y: {
percentage: percentageY,
value: element.scrollTop,
total: maxY,
className: classNameY,
direction: previous ? Math.sign(element.scrollTop - previous.y.value) : 0
}
};
previousScroll.current = scrollInfo;
setScroll(scrollInfo);
}
var throttledUpdate = throttle(update, throttleTime);
var setRef = (0, _react.useCallback)(function (node) {
if (node) {
// When the ref is first set (after mounting)
node.addEventListener("scroll", throttledUpdate);
if (!window.ResizeObserver) {
window.addEventListener("resize", throttledUpdate); // Fallback if ResizeObserver is not available
}
ref.current = node;
throttledUpdate(); // initialization
} else if (ref.current) {
// When unmounting
ref.current.removeEventListener("scroll", throttledUpdate);
if (!window.ResizeObserver) {
window.removeEventListener("resize", throttledUpdate);
}
}
}, []);
return [scroll, setRef, ref];
}
var _default = useScrollInfo;
exports["default"] = _default;
;