UNPKG

@bongione/react-element-scroll-hook

Version:

A react hook to use the scroll information of an element

192 lines (159 loc) 6.32 kB
"use strict"; 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;