@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
JavaScript
"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