UNPKG

use-element-in-view

Version:

A simple React hook to track whether an element is visible in the viewport with the Intersection Observer. This API provides a native way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-leve

164 lines (136 loc) 5.85 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var react = require('react'); function useLatest(value) { var ref = react.useRef(value); ref.current = value; return ref; } function isRefObject(x) { return x && typeof x === 'object' && 'current' in x; } function hasSupport() { return typeof window !== 'undefined' && 'IntersectionObserver' in window && 'IntersectionObserverEntry' in window && 'intersectionRatio' in IntersectionObserverEntry.prototype; } function useElementInView(_temp) { var _ref = _temp === void 0 ? {} : _temp, _ref$ref = _ref.ref, forwardedRef = _ref$ref === void 0 ? null : _ref$ref, _ref$root = _ref.root, root = _ref$root === void 0 ? null : _ref$root, _ref$rootMargin = _ref.rootMargin, rootMargin = _ref$rootMargin === void 0 ? '0px' : _ref$rootMargin, _ref$threshold = _ref.threshold, threshold = _ref$threshold === void 0 ? 0 : _ref$threshold, _ref$defaultInView = _ref.defaultInView, defaultInView = _ref$defaultInView === void 0 ? false : _ref$defaultInView, _ref$disconnectOnceVi = _ref.disconnectOnceVisible, disconnectOnceVisible = _ref$disconnectOnceVi === void 0 ? false : _ref$disconnectOnceVi, onChange = _ref.onChange; var _useState = react.useState({ entry: undefined, elementInView: defaultInView }), observerEntry = _useState[0], setObserverEntry = _useState[1]; // Store our Intersection Observer instance to a ref for its lifecycle. var observerInstanceRef = react.useRef(null); // Element refs. We store the previously tracked element to ensure we only update when the element has changed // Along with the element supplied via the callback ref. var callbackElementRef = react.useRef(null); var prevTrackedElementRef = react.useRef(null); // Helper refs. var isObservingRef = react.useRef(false); var onChangeRef = useLatest(onChange); // Store the threshold as a primitive value to ensure it doesn't change in the deps array // for registerObserver fn when the consumer passes in an inline array. // eg: threshold: [0.25, 0.5] => will be diffed as a new array each render var memoizedThreshold = react.useMemo(function () { return Array.isArray(threshold) ? threshold.join() : threshold; }, [threshold]); var observeElement = react.useCallback(function (node) { if (isObservingRef.current || !observerInstanceRef.current) { return; } isObservingRef.current = true; observerInstanceRef.current.observe(node); }, []); var disconnect = react.useCallback(function () { if (!isObservingRef.current || !observerInstanceRef.current) { return; } observerInstanceRef.current.disconnect(); // clear all refs observerInstanceRef.current = null; isObservingRef.current = false; prevTrackedElementRef.current = null; }, []); // Instantiates the Intersection Observer. // It will determine the element based off how it was provided // and return a warning if no element was found via the options to assign one. var registerObserver = react.useCallback(function () { if (!hasSupport()) { return; } var element = null; if (callbackElementRef.current) { element = callbackElementRef.current; } else if (forwardedRef instanceof HTMLElement) { element = forwardedRef; } else if (isRefObject(forwardedRef)) { element = forwardedRef.current; } // Don't update or recall the register function unless the element has changed. if (prevTrackedElementRef.current === element) return; prevTrackedElementRef.current = element; if (!element) { // eslint-disable-next-line console.warn('🚨 No element has been found, are you sure you correctly assigned a ref?'); return; } // Take our stored threshold and transform it to `number | number[]` that is required // for the IntersectionObserverInit option var transformedThreshold = typeof memoizedThreshold === 'string' ? memoizedThreshold.split(',').map(function (value) { return parseFloat(value); }) : memoizedThreshold; // Ensure we only create the instance once if (!observerInstanceRef.current) { var instance = new IntersectionObserver(function (_ref2) { var entry = _ref2[0]; var isIntersecting = entry.isIntersecting && instance.thresholds.some(function (threshold) { return entry.intersectionRatio >= threshold; }); // Disconnect the instance once the observed entry is in view, and option to disconnectOnceVisible has been set if (isIntersecting && disconnectOnceVisible) { disconnect(); } if (onChangeRef.current) { onChangeRef.current(entry); } else { setObserverEntry({ entry: entry, elementInView: isIntersecting }); } }, { root: root, rootMargin: rootMargin, threshold: transformedThreshold }); observerInstanceRef.current = instance; } observeElement(element); }, [root, rootMargin, memoizedThreshold, disconnectOnceVisible, observeElement, disconnect, forwardedRef, onChangeRef]); var callbackRef = react.useCallback(function (node) { if (node) { callbackElementRef.current = node; registerObserver(); } }, [registerObserver]); react.useEffect(function () { if (forwardedRef) { registerObserver(); } return function () { disconnect(); }; }, [forwardedRef, registerObserver, disconnect]); return { entry: observerEntry.entry, inView: observerEntry.elementInView, assignRef: callbackRef, disconnect: disconnect }; } exports.useElementInView = useElementInView; //# sourceMappingURL=use-element-in-view.cjs.development.js.map