react-in-viewport
Version:
Track React component in viewport using Intersection Observer API
159 lines (153 loc) • 5.47 kB
JavaScript
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["exports", "react", "./constants"], factory);
} else if (typeof exports !== "undefined") {
factory(exports, require("react"), require("./constants"));
} else {
var mod = {
exports: {}
};
factory(mod.exports, global.react, global.constants);
global.useInViewport = mod.exports;
}
})(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (_exports, _react, _constants) {
"use strict";
_exports.__esModule = true;
_exports["default"] = void 0;
var defaultMutationObserverOption = {
attributes: true,
childList: true,
subtree: true
};
var useInViewport = function useInViewport(target, options, config, props) {
if (options === void 0) {
options = _constants.defaultOptions;
}
if (config === void 0) {
config = _constants.defaultConfig;
}
if (props === void 0) {
props = _constants.defaultProps;
}
var _props = props,
onEnterViewport = _props.onEnterViewport,
onLeaveViewport = _props.onLeaveViewport;
var _useState = (0, _react.useState)(),
forceUpdate = _useState[1];
var observer = (0, _react.useRef)();
var inViewportRef = (0, _react.useRef)(false);
var intersected = (0, _react.useRef)(false);
var enterCountRef = (0, _react.useRef)(0);
var leaveCountRef = (0, _react.useRef)(0);
// State to track when target is available
var _useState2 = (0, _react.useState)(Boolean(target.current)),
isTargetReady = _useState2[0],
setIsTargetReady = _useState2[1];
function startObserver(_ref) {
var observerRef = _ref.observerRef;
var targetRef = target.current;
if (targetRef) {
var node = targetRef;
if (node) {
observerRef == null ? void 0 : observerRef.observe(node);
}
}
}
function stopObserver(_ref2) {
var observerRef = _ref2.observerRef;
var targetRef = target.current;
if (targetRef) {
var node = targetRef;
if (node) {
observerRef == null ? void 0 : observerRef.unobserve(node);
}
}
observerRef == null ? void 0 : observerRef.disconnect();
observer.current = null;
}
var handleIntersection = function handleIntersection(entries) {
var entry = entries[0] || {};
var isIntersecting = entry.isIntersecting,
intersectionRatio = entry.intersectionRatio;
var isInViewport = typeof isIntersecting !== 'undefined' ? isIntersecting : intersectionRatio > 0;
// enter
if (!intersected.current && isInViewport) {
intersected.current = true;
onEnterViewport == null ? void 0 : onEnterViewport();
enterCountRef.current += 1;
inViewportRef.current = isInViewport;
forceUpdate(isInViewport);
return;
}
// leave
if (intersected.current && !isInViewport) {
intersected.current = false;
onLeaveViewport == null ? void 0 : onLeaveViewport();
if (config.disconnectOnLeave && observer.current) {
// disconnect observer on leave
observer.current.disconnect();
}
leaveCountRef.current += 1;
inViewportRef.current = isInViewport;
forceUpdate(isInViewport);
}
};
function initIntersectionObserver(_ref3) {
var observerRef = _ref3.observerRef;
if (!observerRef) {
observer.current = new IntersectionObserver(handleIntersection, options);
return observer.current;
}
return observerRef;
}
(0, _react.useEffect)(function () {
var observerRef = observer.current;
// https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
observerRef = initIntersectionObserver({
observerRef: observerRef
});
startObserver({
observerRef: observerRef
});
return function () {
stopObserver({
observerRef: observerRef
});
};
}, [target.current, options, config, onEnterViewport, onLeaveViewport]);
// Use MutationObserver to detect when `target.current` becomes non-null
// only at start up
(0, _react.useEffect)(function () {
var currentElement = target.current;
var mutationObserver = null;
// MutationObserver callback to check when the target ref is assigned
var handleOnChange = function handleOnChange() {
if (target.current && !isTargetReady) {
setIsTargetReady(true);
if (mutationObserver) {
mutationObserver.disconnect();
}
}
};
if (currentElement) {
setIsTargetReady(true); // If target is already available, mark it ready
} else {
// Observe changes to detect when `target.current` becomes non-null
mutationObserver = new MutationObserver(handleOnChange);
mutationObserver.observe(document.body, defaultMutationObserverOption);
}
// Cleanup function to stop observing when the component unmounts
return function () {
if (mutationObserver) {
mutationObserver.disconnect();
}
};
}, [target.current]);
return {
inViewport: inViewportRef.current,
enterCount: enterCountRef.current,
leaveCount: leaveCountRef.current
};
};
var _default = _exports["default"] = useInViewport;
});