UNPKG

@herberthtk/react-native-viewport

Version:

A React Native wrapper to check whether a component is in the view port to track impressions

202 lines (189 loc) 6.71 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _react = _interopRequireWildcard(require("react")); var _reactNative = require("react-native"); var _debounce = _interopRequireDefault(require("lodash/debounce")); var _jsxRuntime = require("react/jsx-runtime"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /** * InViewport Component Props * @typedef {Object} InViewportProps */ /** * InViewport Component State * @typedef {Object} InViewportState */ /** * InViewport Component * * A React Native component that monitors whether it is visible in the viewport * and triggers a callback when visibility changes. Can be used to detect when * a component scrolls into or out of view. */ class InViewport extends _react.Component { myview = /*#__PURE__*/_react.default.createRef(); // Reference to the View component interval = null; // Interval reference for checking visibility lastValue = null; // Last known visibility state subscription = null; // Subscription for dimension change events constructor(props) { super(props); this.state = { rectTop: 0, rectBottom: 0, rectHeight: 0, rectWidth: 0 }; } /** * handleVisibilityChange * * Calls the onChange callback with the updated visibility state. * Uses a debounced version of the function for efficiency. * @param {boolean} isVisible - Whether the component is visible * * Debounce handleVisibilityChange for performance */ handleVisibilityChange = (0, _debounce.default)(isVisible => { if (this.props.onChange) { this.props.onChange(isVisible); } }, 200); /** * componentDidMount * * Lifecycle method that sets up visibility checking and starts watching * when the component mounts. */ componentDidMount() { if (!this.props.disabled) { this.startWatching(); this.isInViewPort(); // Initial check for visibility on mount this.subscription = _reactNative.Dimensions.addEventListener('change', this.isInViewPort); } } /** * componentDidUpdate * * Checks if the disabled prop has changed, and starts or stops * watching accordingly. * @param {InViewportProps} prevProps - Previous props */ componentDidUpdate(prevProps) { if (prevProps.disabled !== this.props.disabled) { if (this.props.disabled) { this.stopWatching(); } else { this.lastValue = null; this.startWatching(); } } } /** * componentWillUnmount * * Lifecycle method that clears the interval and removes event listeners * when the component unmounts. */ componentWillUnmount() { this.stopWatching(); // Remove the event listener when the component unmounts if (this.subscription) { this.subscription.remove(); } } /** * startWatching * * Starts the interval for monitoring visibility and sets up * measurements for the component's position and size. */ startWatching = () => { this.interval = setInterval(() => { if (!this.myview.current) { return; } this.myview.current.measure((_x, _y, width, height, pageX, pageY) => { this.setState({ rectTop: pageY, rectBottom: pageY + height, rectHeight: height, rectWidth: pageX + width }); }); this.isInViewPort(); }, this.props.delay || 1000); }; /** * stopWatching * * Clears the interval that monitors visibility, stopping the checks. */ stopWatching = () => { if (this.interval) { clearInterval(this.interval); } }; /** * isInViewPort * * Determines whether the component is within the viewport based on * its top and bottom positions, the viewport height, and the specified * threshold. Calls handleVisibilityChange if the visibility state changes. */ isInViewPort = () => { // Add defensive programming to ensure props and state are defined before accessing them if (!this.props || !this.state) { console.warn('Props or state is undefined'); return; } let visiblePercentage = 100; // Default to 100% visibility if (this.props?.threshold) { const threshold = this.props.threshold; // Validate the threshold value if (threshold <= 0 || threshold > 1) { console.error('Threshold should be greater than zero and less or equal to one'); } else { visiblePercentage = threshold * 100; } } const windowDimensions = _reactNative.Dimensions.get('window'); const { rectTop, rectBottom, rectHeight } = this.state; //const visiblePercentage = this.props?.threshold * 100 || 100; // Default to 100% visibility // Calculate visible height percentage based on component position in the viewport const visibleHeight = Math.min(rectBottom, windowDimensions.height) - Math.max(rectTop, 0); const visibility = visibleHeight / rectHeight * 100; // Determine visibility based on threshold const isVisible = rectBottom !== 0 && visibility >= visiblePercentage && // Check if at least the specified percentage is visible this.state.rectWidth > 0 && this.state.rectWidth <= windowDimensions.width; if (this.lastValue !== isVisible) { this.lastValue = isVisible; // this.props.onChange(isVisible); this.handleVisibilityChange(isVisible); // Debounced visibility change } }; /** * render * * Renders the View component and its children. */ render() { return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { collapsable: false, ref: this.myview, ...this.props, children: this.props.children }); } } var _default = exports.default = /*#__PURE__*/(0, _react.memo)(InViewport); //# sourceMappingURL=index.js.map