UNPKG

react-use-motion-measure

Version:

measure view bounds

243 lines (224 loc) 7.79 kB
'use strict'; var react = require('react'); var createDebounce = require('debounce'); var framerMotion = require('framer-motion'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var createDebounce__default = /*#__PURE__*/_interopDefaultLegacy(createDebounce); function useMotionMeasure(_temp) { var _ref = _temp === void 0 ? { debounce: 0, scroll: false, offsetSize: false } : _temp, debounce = _ref.debounce, scroll = _ref.scroll, polyfill = _ref.polyfill, offsetSize = _ref.offsetSize; var ResizeObserver = polyfill || (typeof window === 'undefined' ? function ResizeObserver() {} : window.ResizeObserver); if (!ResizeObserver) { throw new Error('This browser does not support ResizeObserver out of the box. See: https://github.com/OlegWock/react-use-motion-measure/#resize-observer-polyfills'); } var left = framerMotion.useMotionValue(0); var top = framerMotion.useMotionValue(0); var width = framerMotion.useMotionValue(0); var height = framerMotion.useMotionValue(0); var bottom = framerMotion.useMotionValue(0); var right = framerMotion.useMotionValue(0); var x = framerMotion.useMotionValue(0); var y = framerMotion.useMotionValue(0); // keep all state in a ref var state = react.useRef({ element: null, measuredAtLeastOnce: false, scrollContainers: null, resizeObserver: null, lastBounds: getLastBounds() }); // set actual debounce values early, so effects know if they should react accordingly var scrollDebounce = debounce ? typeof debounce === 'number' ? debounce : debounce.scroll : null; var resizeDebounce = debounce ? typeof debounce === 'number' ? debounce : debounce.resize : null; // make sure to update state only as long as the component is truly mounted var mounted = react.useRef(false); react.useEffect(function () { mounted.current = true; return function () { return void (mounted.current = false); }; }); // memoize handlers, so event-listeners know when they should update var _useMemo = react.useMemo(function () { var callback = function callback() { if (!state.current.element) return; var _ref2 = state.current.element.getBoundingClientRect(), left = _ref2.left, top = _ref2.top, width = _ref2.width, height = _ref2.height, bottom = _ref2.bottom, right = _ref2.right, x = _ref2.x, y = _ref2.y; var size = { left: left, top: top, width: width, height: height, bottom: bottom, right: right, x: x, y: y }; if (state.current.element instanceof HTMLElement && offsetSize) { size.height = state.current.element.offsetHeight; size.width = state.current.element.offsetWidth; } Object.freeze(size); if (mounted.current && !areBoundsEqual(state.current.lastBounds, size)) { if (state.current.measuredAtLeastOnce) { set(state.current.lastBounds = size); } else { jump(state.current.lastBounds = size); } } }; return [callback, resizeDebounce ? createDebounce__default["default"](callback, resizeDebounce) : callback, scrollDebounce ? createDebounce__default["default"](callback, scrollDebounce) : callback]; }, [set, jump, offsetSize, scrollDebounce, resizeDebounce]), forceRefresh = _useMemo[0], resizeChange = _useMemo[1], scrollChange = _useMemo[2]; // cleanup current scroll-listeners / observers function removeListeners() { if (state.current.scrollContainers) { state.current.scrollContainers.forEach(function (element) { return element.removeEventListener('scroll', scrollChange, true); }); state.current.scrollContainers = null; } if (state.current.resizeObserver) { state.current.resizeObserver.disconnect(); state.current.resizeObserver = null; } } // add scroll-listeners / observers function addListeners() { if (!state.current.element) return; state.current.resizeObserver = new ResizeObserver(scrollChange); state.current.resizeObserver.observe(state.current.element); if (scroll && state.current.scrollContainers) { state.current.scrollContainers.forEach(function (scrollContainer) { return scrollContainer.addEventListener('scroll', scrollChange, { capture: true, passive: true }); }); } } function getLastBounds() { return { left: left.get(), top: top.get(), width: width.get(), height: height.get(), bottom: bottom.get(), right: right.get(), x: x.get(), y: y.get() }; } function set(bounds) { left.set(bounds.left); top.set(bounds.top); width.set(bounds.width); height.set(bounds.height); bottom.set(bounds.bottom); right.set(bounds.right); x.set(bounds.x); y.set(bounds.y); } function jump(bounds) { left.jump(bounds.left); top.jump(bounds.top); width.jump(bounds.width); height.jump(bounds.height); bottom.jump(bounds.bottom); right.jump(bounds.right); x.jump(bounds.x); y.jump(bounds.y); } // the ref we expose to the user var ref = function ref(node) { if (!node || node === state.current.element) return; removeListeners(); state.current.element = node; state.current.measuredAtLeastOnce = false; state.current.scrollContainers = findScrollContainers(node); addListeners(); }; // add general event listeners useOnWindowScroll(scrollChange, Boolean(scroll)); useOnWindowResize(resizeChange); // respond to changes that are relevant for the listeners react.useEffect(function () { removeListeners(); addListeners(); }, [scroll, scrollChange, resizeChange]); // remove all listeners when the components unmounts react.useEffect(function () { return removeListeners; }, []); return [ref, { left: left, top: top, width: width, height: height, bottom: bottom, right: right, x: x, y: y }, forceRefresh]; } // Adds native resize listener to window function useOnWindowResize(onWindowResize) { react.useEffect(function () { var cb = onWindowResize; window.addEventListener('resize', cb); return function () { return void window.removeEventListener('resize', cb); }; }, [onWindowResize]); } function useOnWindowScroll(onScroll, enabled) { react.useEffect(function () { if (enabled) { var _cb = onScroll; window.addEventListener('scroll', _cb, { capture: true, passive: true }); return function () { return void window.removeEventListener('scroll', _cb, true); }; } }, [onScroll, enabled]); } // Returns a list of scroll offsets function findScrollContainers(element) { var result = []; if (!element || element === document.body) return result; var _window$getComputedSt = window.getComputedStyle(element), overflow = _window$getComputedSt.overflow, overflowX = _window$getComputedSt.overflowX, overflowY = _window$getComputedSt.overflowY; if ([overflow, overflowX, overflowY].some(function (prop) { return prop === 'auto' || prop === 'scroll'; })) result.push(element); return [].concat(result, findScrollContainers(element.parentElement)); } // Checks if element boundaries are equal var keys = ['x', 'y', 'top', 'bottom', 'left', 'right', 'width', 'height']; var areBoundsEqual = function areBoundsEqual(a, b) { return keys.every(function (key) { return a[key] === b[key]; }); }; module.exports = useMotionMeasure;