react-cool-inview
Version:
React hook to monitor an element enters or leaves the viewport (or another element).
200 lines (169 loc) • 6.11 kB
JavaScript
import { useRef, useState, useCallback, useEffect, cloneElement } from 'react';
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
var useLatest = (function (val) {
var ref = useRef(val);
ref.current = val;
return ref;
});
var observerErr = "💡 react-cool-inview: the browser doesn't support Intersection Observer, please install polyfill: https://github.com/wellyshen/react-cool-inview#intersection-observer-polyfill";
var observerWarn = "💡 react-cool-inview: the browser doesn't support Intersection Observer v2, fallback to v1 behavior";
var useInView = function useInView(_temp) {
var _ref = _temp === void 0 ? {} : _temp,
root = _ref.root,
rootMargin = _ref.rootMargin,
_ref$threshold = _ref.threshold,
threshold = _ref$threshold === void 0 ? 0 : _ref$threshold,
trackVisibility = _ref.trackVisibility,
delay = _ref.delay,
unobserveOnEnter = _ref.unobserveOnEnter,
onChange = _ref.onChange,
onEnter = _ref.onEnter,
onLeave = _ref.onLeave;
var _useState = useState({
inView: false,
scrollDirection: {}
}),
state = _useState[0],
setState = _useState[1];
var prevInViewRef = useRef(false);
var prevPosRef = useRef({});
var observerRef = useRef();
var warnedRef = useRef(false);
var onChangeRef = useLatest(onChange);
var onEnterRef = useLatest(onEnter);
var onLeaveRef = useLatest(onLeave);
var ref = useRef();
var unobserve = useCallback(function () {
if (observerRef.current) {
observerRef.current.disconnect();
prevPosRef.current = {};
}
}, []);
var observe = useCallback(function (element) {
if (element && element !== ref.current) {
unobserve();
ref.current = element;
}
if (observerRef.current && ref.current) observerRef.current.observe(ref.current);
}, [unobserve]);
var updatePosition = useCallback(function () {
if (!ref.current) return;
var _ref$current$getBound = ref.current.getBoundingClientRect(),
x = _ref$current$getBound.x,
y = _ref$current$getBound.y;
prevPosRef.current = {
x: x,
y: y
};
}, [ref]);
useEffect(function () {
if (!("IntersectionObserver" in window) || !("IntersectionObserverEntry" in window)) {
console.error(observerErr);
return function () {
return null;
};
}
var isActive = true; // eslint-disable-next-line compat/compat
observerRef.current = new IntersectionObserver(function (_ref2) {
var entry = _ref2[0];
var intersectionRatio = entry.intersectionRatio,
isIntersecting = entry.isIntersecting,
_entry$boundingClient = entry.boundingClientRect,
x = _entry$boundingClient.x,
y = _entry$boundingClient.y,
isVisible = entry.isVisible;
var scrollDirection = {};
var min = Array.isArray(threshold) ? Math.min.apply(Math, threshold) : threshold;
var inView = isIntersecting !== undefined ? isIntersecting : intersectionRatio > 0;
inView = min > 0 ? intersectionRatio >= min : inView; // @ts-ignore
if (x < prevPosRef.current.x) scrollDirection.horizontal = "left"; // @ts-ignore
if (x > prevPosRef.current.x) scrollDirection.horizontal = "right";
prevPosRef.current.x = x; // @ts-ignore
if (y < prevPosRef.current.y) scrollDirection.vertical = "up"; // @ts-ignore
if (y > prevPosRef.current.y) scrollDirection.vertical = "down";
prevPosRef.current.y = y;
var e = {
entry: entry,
scrollDirection: scrollDirection,
observe: observe,
unobserve: unobserve
};
if (trackVisibility) {
if (isVisible === undefined && !warnedRef.current) {
console.warn(observerWarn);
warnedRef.current = true;
}
if (isVisible !== undefined) inView = isVisible;
}
if (inView && !prevInViewRef.current) {
if (unobserveOnEnter) unobserve();
if (onEnterRef.current) onEnterRef.current(e);
}
if (!inView && prevInViewRef.current && onLeaveRef.current) onLeaveRef.current(e);
if (onChangeRef.current) onChangeRef.current(_extends({}, e, {
inView: inView
}));
if (isActive) setState({
inView: inView,
scrollDirection: scrollDirection,
entry: entry
});
prevInViewRef.current = inView;
}, {
root: root,
rootMargin: rootMargin,
threshold: threshold,
trackVisibility: trackVisibility,
delay: delay
});
observe();
return function () {
isActive = false;
unobserve();
}; // eslint-disable-next-line react-hooks/exhaustive-deps
}, [unobserveOnEnter, root, rootMargin, // eslint-disable-next-line react-hooks/exhaustive-deps
JSON.stringify(threshold), trackVisibility, delay, observe, unobserve]);
return _extends({}, state, {
observe: observe,
unobserve: unobserve,
updatePosition: updatePosition
});
};
var _excluded = ["children"],
_excluded2 = ["observe"];
var InView = function InView(_ref) {
var children = _ref.children,
props = _objectWithoutPropertiesLoose(_ref, _excluded);
var _useInView = useInView(props),
observe = _useInView.observe,
rest = _objectWithoutPropertiesLoose(_useInView, _excluded2);
return /*#__PURE__*/cloneElement(children, _extends({
observe: observe
}, rest));
};
export { InView, useInView };