UNPKG

@herberthtk/react-native-viewport

Version:

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

195 lines (182 loc) 5.6 kB
"use strict"; import React, { Component, memo } from 'react'; import { View, Dimensions } from 'react-native'; import debounce from 'lodash/debounce'; /** * InViewport Component Props * @typedef {Object} InViewportProps */ /** * InViewport Component State * @typedef {Object} InViewportState */ import { jsx as _jsx } from "react/jsx-runtime"; /** * 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 Component { myview = /*#__PURE__*/React.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 = debounce(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 = 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 = 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__*/_jsx(View, { collapsable: false, ref: this.myview, ...this.props, children: this.props.children }); } } export default /*#__PURE__*/memo(InViewport); //# sourceMappingURL=index.js.map