framer-motion
Version:
A simple and powerful React animation library
99 lines (96 loc) • 4.45 kB
JavaScript
import { useRef, useEffect } from 'react';
import { AnimationType } from '../../../render/utils/types.mjs';
import { warnOnce } from '../../../utils/warn-once.mjs';
import { observeIntersection } from './observers.mjs';
function useViewport(_a) {
var visualElement = _a.visualElement, whileInView = _a.whileInView, onViewportEnter = _a.onViewportEnter, onViewportLeave = _a.onViewportLeave, _b = _a.viewport, viewport = _b === void 0 ? {} : _b;
var state = useRef({
hasEnteredView: false,
isInView: false,
});
var shouldObserve = Boolean(whileInView || onViewportEnter || onViewportLeave);
if (viewport.once && state.current.hasEnteredView)
shouldObserve = false;
var useObserver = typeof IntersectionObserver === "undefined"
? useMissingIntersectionObserver
: useIntersectionObserver;
useObserver(shouldObserve, state.current, visualElement, viewport);
}
var thresholdNames = {
some: 0,
all: 1,
};
function useIntersectionObserver(shouldObserve, state, visualElement, _a) {
var root = _a.root, rootMargin = _a.margin, _b = _a.amount, amount = _b === void 0 ? "some" : _b, once = _a.once;
useEffect(function () {
if (!shouldObserve)
return;
var options = {
root: root === null || root === void 0 ? void 0 : root.current,
rootMargin: rootMargin,
threshold: typeof amount === "number" ? amount : thresholdNames[amount],
};
var intersectionCallback = function (entry) {
var _a;
var isIntersecting = entry.isIntersecting;
/**
* If there's been no change in the viewport state, early return.
*/
if (state.isInView === isIntersecting)
return;
state.isInView = isIntersecting;
/**
* Handle hasEnteredView. If this is only meant to run once, and
* element isn't visible, early return. Otherwise set hasEnteredView to true.
*/
if (once && !isIntersecting && state.hasEnteredView) {
return;
}
else if (isIntersecting) {
state.hasEnteredView = true;
}
(_a = visualElement.animationState) === null || _a === void 0 ? void 0 : _a.setActive(AnimationType.InView, isIntersecting);
/**
* Use the latest committed props rather than the ones in scope
* when this observer is created
*/
var props = visualElement.getProps();
var callback = isIntersecting
? props.onViewportEnter
: props.onViewportLeave;
callback === null || callback === void 0 ? void 0 : callback(entry);
};
return observeIntersection(visualElement.getInstance(), options, intersectionCallback);
}, [shouldObserve, root, rootMargin, amount]);
}
/**
* If IntersectionObserver is missing, we activate inView and fire onViewportEnter
* on mount. This way, the page will be in the state the author expects users
* to see it in for everyone.
*/
function useMissingIntersectionObserver(shouldObserve, state, visualElement, _a) {
var _b = _a.fallback, fallback = _b === void 0 ? true : _b;
useEffect(function () {
if (!shouldObserve || !fallback)
return;
if (process.env.NODE_ENV !== "production") {
warnOnce(false, "IntersectionObserver not available on this device. whileInView animations will trigger on mount.");
}
/**
* Fire this in an rAF because, at this point, the animation state
* won't have flushed for the first time and there's certain logic in
* there that behaves differently on the initial animation.
*
* This hook should be quite rarely called so setting this in an rAF
* is preferred to changing the behaviour of the animation state.
*/
requestAnimationFrame(function () {
var _a;
state.hasEnteredView = true;
var onViewportEnter = visualElement.getProps().onViewportEnter;
onViewportEnter === null || onViewportEnter === void 0 ? void 0 : onViewportEnter(null);
(_a = visualElement.animationState) === null || _a === void 0 ? void 0 : _a.setActive(AnimationType.InView, true);
});
}, [shouldObserve]);
}
export { useViewport };