react-intersection-observer-hook
Version:
React hook to use IntersectionObserver declaratively.
127 lines (123 loc) • 3.85 kB
JavaScript
// src/use-intersection-observer.ts
import { useCallback, useRef, useState } from "react";
// src/utils.ts
function createObserverCache() {
const cachesByRoot = /* @__PURE__ */ new Map();
function getObserver({
root,
rootMargin,
threshold
}) {
let cacheByRoot = cachesByRoot.get(root);
if (!cacheByRoot) {
cacheByRoot = /* @__PURE__ */ new Map();
cachesByRoot.set(root, cacheByRoot);
}
const cacheKey = JSON.stringify({ rootMargin, threshold });
let cachedObserver = cacheByRoot.get(cacheKey);
if (!cachedObserver) {
const entryCallbacks = /* @__PURE__ */ new Map();
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
const callback = entryCallbacks.get(entry.target);
callback?.(entry);
});
},
{ root, rootMargin, threshold }
);
cachedObserver = { observer, entryCallbacks };
cacheByRoot.set(cacheKey, cachedObserver);
}
return {
observe: (node, callback) => {
cachedObserver.entryCallbacks.set(node, callback);
cachedObserver.observer.observe(node);
},
unobserve: (node) => {
cachedObserver.entryCallbacks.delete(node);
cachedObserver.observer.unobserve(node);
}
};
}
return { getObserver };
}
// src/use-intersection-observer.ts
var DEFAULT_ROOT_MARGIN = "0px";
var DEFAULT_THRESHOLD = [0];
var observerCache = createObserverCache();
function useIntersectionObserver(args) {
const rootMargin = args?.rootMargin ?? DEFAULT_ROOT_MARGIN;
const threshold = args?.threshold ?? DEFAULT_THRESHOLD;
const nodeRef = useRef(null);
const rootRef = useRef(null);
const observerRef = useRef(null);
const [entry, setEntry] = useState();
const reinitializeObserver = useCallback(() => {
function cleanupObserver() {
const observer = observerRef.current;
const node = nodeRef.current;
if (node) {
observer?.unobserve(node);
setEntry(void 0);
}
observerRef.current = null;
}
function initializeObserver() {
const node = nodeRef.current;
if (!node) return;
const observer = observerCache.getObserver({
root: rootRef.current,
rootMargin,
threshold
});
observer.observe(node, (observedEntry) => {
setEntry(observedEntry);
});
observerRef.current = observer;
}
cleanupObserver();
initializeObserver();
}, [rootMargin, threshold]);
const refCallback = useCallback(
(node) => {
nodeRef.current = node;
reinitializeObserver();
return () => {
nodeRef.current = null;
reinitializeObserver();
};
},
[reinitializeObserver]
);
const rootRefCallback = useCallback(
(rootNode) => {
rootRef.current = rootNode;
reinitializeObserver();
return () => {
rootRef.current = null;
reinitializeObserver();
};
},
[reinitializeObserver]
);
return [refCallback, { entry, rootRef: rootRefCallback }];
}
var use_intersection_observer_default = useIntersectionObserver;
// src/use-track-visibility.ts
import { useState as useState2 } from "react";
function useTrackVisibility(args) {
const { once, ...rest } = args ?? {};
const [ref, result] = use_intersection_observer_default(rest);
const isVisible = Boolean(result.entry?.isIntersecting);
const [isVisibleOnce, setIsVisibleOnce] = useState2(isVisible);
if (once && isVisible && !isVisibleOnce) {
setIsVisibleOnce(true);
}
return [ref, { ...result, isVisible: once ? isVisibleOnce : isVisible }];
}
var use_track_visibility_default = useTrackVisibility;
export {
use_intersection_observer_default as useIntersectionObserver,
use_track_visibility_default as useTrackVisibility
};