@futurejj/react-native-visibility-sensor 1.3.22 → 1.4.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -24
- package/lib/commonjs/VisibilitySensor.js +94 -13
- package/lib/commonjs/VisibilitySensor.js.map +1 -1
- package/lib/module/VisibilitySensor.js +95 -14
- package/lib/module/VisibilitySensor.js.map +1 -1
- package/lib/typescript/VisibilitySensor.d.ts.map +1 -1
- package/lib/typescript/visibilitySensor.types.d.ts +1 -0
- package/lib/typescript/visibilitySensor.types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/VisibilitySensor.tsx +134 -15
- package/src/visibilitySensor.types.ts +1 -0
package/README.md
CHANGED
|
@@ -26,32 +26,28 @@ npm install @futurejj/react-native-visibility-sensor
|
|
|
26
26
|
|
|
27
27
|
## Usage
|
|
28
28
|
|
|
29
|
-
```
|
|
29
|
+
```tsx
|
|
30
30
|
import React from 'react';
|
|
31
31
|
import { ScrollView, Text } from 'react-native';
|
|
32
32
|
import { VisibilitySensor } from '@futurejj/react-native-visibility-sensor';
|
|
33
33
|
|
|
34
34
|
export default function VisibilitySensorExample() {
|
|
35
|
-
const [
|
|
36
|
-
|
|
37
|
-
function checkVisible(isVisible: boolean) {
|
|
38
|
-
if (isVisible) {
|
|
39
|
-
setIsInView(isVisible);
|
|
40
|
-
} else {
|
|
41
|
-
setIsInView(isVisible);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
35
|
+
const [isVisible, setIsVisible] = React.useState(false);
|
|
36
|
+
const [percentVisible, setPercentVisible] = useState<number>(0);
|
|
44
37
|
|
|
45
38
|
return (
|
|
46
39
|
<ScrollView>
|
|
47
40
|
<VisibilitySensor
|
|
48
|
-
onChange={
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<Text>This View is currently visible? {
|
|
41
|
+
onChange={setIsVisible}
|
|
42
|
+
onPercentChange={setPercentVisible} // optional callback for % change
|
|
43
|
+
threshold={{ top: 100, bottom: 100 }}
|
|
44
|
+
style={[styles.item, { backgroundColor: isVisible ? 'green' : 'red' }]}>
|
|
45
|
+
|
|
46
|
+
{/* Visibility state */}
|
|
47
|
+
<Text>This View is currently visible? {isVisible ? 'yes' : 'no'}</Text>
|
|
48
|
+
|
|
49
|
+
{/* Percent visibility state */}
|
|
50
|
+
<Text>{`Percent visible: ${percentVisible}%`}</Text>
|
|
55
51
|
</VisibilitySensor>
|
|
56
52
|
</ScrollView>
|
|
57
53
|
);
|
|
@@ -61,13 +57,14 @@ export default function VisibilitySensorExample() {
|
|
|
61
57
|
|
|
62
58
|
`VisibilitySensorProps` extends `ViewProps` from React Native, which includes common properties for all views, such as `style`, `onLayout`, etc.
|
|
63
59
|
|
|
64
|
-
| Property
|
|
65
|
-
|
|
|
66
|
-
| onChange
|
|
67
|
-
|
|
|
68
|
-
|
|
|
69
|
-
|
|
|
70
|
-
|
|
|
60
|
+
| Property | Type | Required | Description |
|
|
61
|
+
| --------------- | ------------------------------------------------------- | -------- | ------------------------------------------------------------ |
|
|
62
|
+
| onChange | (visible: boolean) => void | Yes | Callback function that fires when visibility changes. |
|
|
63
|
+
| onPercentChange | (percentVisible: number) => void | No | Callback function that fires when visibility % changes. |
|
|
64
|
+
| disabled | boolean | No | If `true`, disables the sensor. |
|
|
65
|
+
| triggerOnce | boolean | No | If `true`, the sensor will only trigger once. |
|
|
66
|
+
| delay | number \| undefined | No | The delay in milliseconds before the sensor triggers. |
|
|
67
|
+
| threshold | [VisibilitySensorThreshold](#visibilitysensorthreshold) | No | Defines the part of the view that must be visible for the sensor to trigger. |
|
|
71
68
|
|
|
72
69
|
Additionally, all properties from `ViewProps` are also applicable.
|
|
73
70
|
|
|
@@ -8,6 +8,14 @@ var _react = _interopRequireWildcard(require("react"));
|
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
10
10
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
11
|
+
var MeasurementState = /*#__PURE__*/function (MeasurementState) {
|
|
12
|
+
MeasurementState["IDLE"] = "IDLE";
|
|
13
|
+
// Not yet measured
|
|
14
|
+
MeasurementState["MEASURING"] = "MEASURING";
|
|
15
|
+
// Measurement in progress
|
|
16
|
+
MeasurementState["MEASURED"] = "MEASURED"; // Has valid measurements
|
|
17
|
+
return MeasurementState;
|
|
18
|
+
}(MeasurementState || {});
|
|
11
19
|
function useInterval(callback, delay) {
|
|
12
20
|
const savedCallback = (0, _react.useRef)(callback);
|
|
13
21
|
(0, _react.useEffect)(() => {
|
|
@@ -24,6 +32,7 @@ function useInterval(callback, delay) {
|
|
|
24
32
|
const VisibilitySensor = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
|
|
25
33
|
const {
|
|
26
34
|
onChange,
|
|
35
|
+
onPercentChange,
|
|
27
36
|
disabled = false,
|
|
28
37
|
triggerOnce = false,
|
|
29
38
|
delay,
|
|
@@ -31,10 +40,14 @@ const VisibilitySensor = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
|
|
|
31
40
|
children,
|
|
32
41
|
...rest
|
|
33
42
|
} = props;
|
|
34
|
-
const localRef = (0, _react.useRef)(null);
|
|
35
43
|
(0, _react.useImperativeHandle)(ref, () => ({
|
|
36
44
|
getInnerRef: () => localRef.current
|
|
37
45
|
}));
|
|
46
|
+
const window = (0, _reactNative.useWindowDimensions)();
|
|
47
|
+
const localRef = (0, _react.useRef)(null);
|
|
48
|
+
const isMountedRef = (0, _react.useRef)(true);
|
|
49
|
+
const measurementStateRef = (0, _react.useRef)(MeasurementState.IDLE);
|
|
50
|
+
const lastPercentRef = (0, _react.useRef)(undefined);
|
|
38
51
|
const [rectDimensions, setRectDimensions] = (0, _react.useState)({
|
|
39
52
|
rectTop: 0,
|
|
40
53
|
rectBottom: 0,
|
|
@@ -45,12 +58,18 @@ const VisibilitySensor = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
|
|
|
45
58
|
});
|
|
46
59
|
const [lastValue, setLastValue] = (0, _react.useState)(undefined);
|
|
47
60
|
const [active, setActive] = (0, _react.useState)(false);
|
|
48
|
-
const
|
|
49
|
-
const measureInnerView = () => {
|
|
61
|
+
const measureInnerView = (0, _react.useCallback)(() => {
|
|
50
62
|
/* Check if the sensor is active to prevent unnecessary measurements
|
|
51
63
|
This avoids running measurements when the sensor is disabled or stopped */
|
|
52
|
-
if (!active)
|
|
64
|
+
if (!active || !isMountedRef.current || measurementStateRef.current === MeasurementState.MEASURING) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
measurementStateRef.current = MeasurementState.MEASURING;
|
|
53
68
|
localRef.current?.measure((_x, _y, width, height, pageX, pageY) => {
|
|
69
|
+
// Check if component is still mounted before setting state because measurement can be asynchronous
|
|
70
|
+
if (!isMountedRef.current) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
54
73
|
const dimensions = {
|
|
55
74
|
rectTop: pageY,
|
|
56
75
|
rectBottom: pageY + height,
|
|
@@ -61,12 +80,13 @@ const VisibilitySensor = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
|
|
|
61
80
|
};
|
|
62
81
|
if (rectDimensions.rectTop !== dimensions.rectTop || rectDimensions.rectBottom !== dimensions.rectBottom || rectDimensions.rectLeft !== dimensions.rectLeft || rectDimensions.rectRight !== dimensions.rectRight || rectDimensions.rectWidth !== dimensions.rectWidth || rectDimensions.rectHeight !== dimensions.rectHeight) {
|
|
63
82
|
setRectDimensions(dimensions);
|
|
64
|
-
/* Set hasMeasuredRef to true to indicate that a valid measurement has been taken
|
|
65
|
-
This ensures visibility checks only proceed after initial measurement */
|
|
66
|
-
hasMeasuredRef.current = true;
|
|
67
83
|
}
|
|
84
|
+
|
|
85
|
+
/* Set measurementStateRef to MEASURED to indicate that a valid measurement has
|
|
86
|
+
been taken. This ensures visibility checks only proceed after initial measurement */
|
|
87
|
+
measurementStateRef.current = MeasurementState.MEASURED;
|
|
68
88
|
});
|
|
69
|
-
};
|
|
89
|
+
}, [active, rectDimensions]);
|
|
70
90
|
useInterval(measureInnerView, delay || 100);
|
|
71
91
|
const startWatching = (0, _react.useCallback)(() => {
|
|
72
92
|
if (!active) setActive(true);
|
|
@@ -76,9 +96,37 @@ const VisibilitySensor = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
|
|
|
76
96
|
setActive(false);
|
|
77
97
|
/* Reset measurement state when stopping to ensure fresh measurements
|
|
78
98
|
when the sensor is reactivated */
|
|
79
|
-
|
|
99
|
+
measurementStateRef.current = MeasurementState.IDLE; // Reset state
|
|
80
100
|
}
|
|
81
101
|
}, [active]);
|
|
102
|
+
|
|
103
|
+
// Effect to trigger initial measurement when component becomes active:
|
|
104
|
+
(0, _react.useEffect)(() => {
|
|
105
|
+
let timer;
|
|
106
|
+
if (active && measurementStateRef.current === MeasurementState.IDLE) {
|
|
107
|
+
// Use setTimeout with 0 delay to ensure layout is complete
|
|
108
|
+
timer = setTimeout(() => {
|
|
109
|
+
measureInnerView();
|
|
110
|
+
}, 0);
|
|
111
|
+
}
|
|
112
|
+
return () => {
|
|
113
|
+
if (timer) clearTimeout(timer);
|
|
114
|
+
};
|
|
115
|
+
}, [active, measureInnerView]);
|
|
116
|
+
|
|
117
|
+
// Reset measurement state when dimensions change:
|
|
118
|
+
(0, _react.useEffect)(() => {
|
|
119
|
+
if (isMountedRef.current && measurementStateRef.current === MeasurementState.MEASURED) {
|
|
120
|
+
// Reset measurement state to force remeasurement with new dimensions
|
|
121
|
+
measurementStateRef.current = MeasurementState.IDLE;
|
|
122
|
+
}
|
|
123
|
+
}, [window]);
|
|
124
|
+
(0, _react.useEffect)(() => {
|
|
125
|
+
isMountedRef.current = true;
|
|
126
|
+
return () => {
|
|
127
|
+
isMountedRef.current = false;
|
|
128
|
+
};
|
|
129
|
+
}, []);
|
|
82
130
|
(0, _react.useEffect)(() => {
|
|
83
131
|
if (!disabled) {
|
|
84
132
|
startWatching();
|
|
@@ -91,8 +139,9 @@ const VisibilitySensor = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
|
|
|
91
139
|
/* Ensure visibility checks only run when the sensor is active and
|
|
92
140
|
at least one measurement has been completed. This prevents
|
|
93
141
|
premature visibility calculations with invalid or stale dimensions */
|
|
94
|
-
if (!active || !
|
|
95
|
-
|
|
142
|
+
if (!active || measurementStateRef.current !== MeasurementState.MEASURED || !isMountedRef.current) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
96
145
|
const isVisible = rectDimensions.rectTop + (threshold.top || 0) <= window.height &&
|
|
97
146
|
// Top edge is within the bottom of the window
|
|
98
147
|
rectDimensions.rectBottom - (threshold.bottom || 0) >= 0 &&
|
|
@@ -101,6 +150,39 @@ const VisibilitySensor = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
|
|
|
101
150
|
// Left edge is within the right of the window
|
|
102
151
|
rectDimensions.rectRight - (threshold.right || 0) >= 0; // Right edge is within the left of the window
|
|
103
152
|
|
|
153
|
+
// Calculate percent visible if callback is requested / provided
|
|
154
|
+
if (onPercentChange && rectDimensions.rectWidth > 0 && rectDimensions.rectHeight > 0 && isVisible // Don't perform % calculation if not visible for efficiency
|
|
155
|
+
) {
|
|
156
|
+
// Thresholds reduce the effective viewport
|
|
157
|
+
const viewportTop = 0 + (threshold.top || 0);
|
|
158
|
+
const viewportBottom = window.height - (threshold.bottom || 0);
|
|
159
|
+
const viewportLeft = 0 + (threshold.left || 0);
|
|
160
|
+
const viewportRight = window.width - (threshold.right || 0);
|
|
161
|
+
|
|
162
|
+
// Calculate the visible portion of the element within the reduced viewport
|
|
163
|
+
const visibleTop = Math.max(viewportTop, rectDimensions.rectTop);
|
|
164
|
+
const visibleBottom = Math.min(viewportBottom, rectDimensions.rectBottom);
|
|
165
|
+
const visibleLeft = Math.max(viewportLeft, rectDimensions.rectLeft);
|
|
166
|
+
const visibleRight = Math.min(viewportRight, rectDimensions.rectRight);
|
|
167
|
+
|
|
168
|
+
// Calculate visible dimensions
|
|
169
|
+
const visibleHeight = Math.max(0, visibleBottom - visibleTop);
|
|
170
|
+
const visibleWidth = Math.max(0, visibleRight - visibleLeft);
|
|
171
|
+
|
|
172
|
+
// Calculate percent visible based on actual element area
|
|
173
|
+
const visibleArea = visibleHeight * visibleWidth;
|
|
174
|
+
const totalArea = rectDimensions.rectHeight * rectDimensions.rectWidth;
|
|
175
|
+
const percentVisible = totalArea > 0 ? Math.round(visibleArea / totalArea * 100) : 0;
|
|
176
|
+
|
|
177
|
+
// Only fire callback if percent has changed
|
|
178
|
+
if (lastPercentRef.current !== percentVisible) {
|
|
179
|
+
lastPercentRef.current = percentVisible;
|
|
180
|
+
onPercentChange(percentVisible);
|
|
181
|
+
}
|
|
182
|
+
} else if (onPercentChange && rectDimensions.rectWidth > 0 && rectDimensions.rectHeight > 0 && !isVisible // If not visible, always report 0%
|
|
183
|
+
) {
|
|
184
|
+
onPercentChange(0);
|
|
185
|
+
}
|
|
104
186
|
if (lastValue !== isVisible) {
|
|
105
187
|
setLastValue(isVisible);
|
|
106
188
|
onChange(isVisible);
|
|
@@ -108,8 +190,7 @@ const VisibilitySensor = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
|
|
|
108
190
|
stopWatching();
|
|
109
191
|
}
|
|
110
192
|
}
|
|
111
|
-
|
|
112
|
-
}, [rectDimensions, lastValue, active]);
|
|
193
|
+
}, [rectDimensions, window, lastValue, active, onPercentChange, threshold, onChange, triggerOnce, stopWatching]);
|
|
113
194
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
114
195
|
ref: localRef,
|
|
115
196
|
...rest,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_jsxRuntime","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","useInterval","callback","delay","savedCallback","useRef","useEffect","current","undefined","id","setInterval","clearInterval","VisibilitySensor","forwardRef","props","ref","onChange","disabled","triggerOnce","threshold","children","rest","localRef","
|
|
1
|
+
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_jsxRuntime","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","MeasurementState","useInterval","callback","delay","savedCallback","useRef","useEffect","current","undefined","id","setInterval","clearInterval","VisibilitySensor","forwardRef","props","ref","onChange","onPercentChange","disabled","triggerOnce","threshold","children","rest","useImperativeHandle","getInnerRef","localRef","window","useWindowDimensions","isMountedRef","measurementStateRef","IDLE","lastPercentRef","rectDimensions","setRectDimensions","useState","rectTop","rectBottom","rectLeft","rectRight","rectWidth","rectHeight","lastValue","setLastValue","active","setActive","measureInnerView","useCallback","MEASURING","measure","_x","_y","width","height","pageX","pageY","dimensions","MEASURED","startWatching","stopWatching","timer","setTimeout","clearTimeout","isVisible","top","bottom","left","right","viewportTop","viewportBottom","viewportLeft","viewportRight","visibleTop","Math","max","visibleBottom","min","visibleLeft","visibleRight","visibleHeight","visibleWidth","visibleArea","totalArea","percentVisible","round","jsx","View","_default","exports"],"sourceRoot":"../../src","sources":["VisibilitySensor.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import React, { useCallback, useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { useWindowDimensions, View } from 'react-native';
|
|
5
5
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
|
+
var MeasurementState = /*#__PURE__*/function (MeasurementState) {
|
|
7
|
+
MeasurementState["IDLE"] = "IDLE";
|
|
8
|
+
// Not yet measured
|
|
9
|
+
MeasurementState["MEASURING"] = "MEASURING";
|
|
10
|
+
// Measurement in progress
|
|
11
|
+
MeasurementState["MEASURED"] = "MEASURED"; // Has valid measurements
|
|
12
|
+
return MeasurementState;
|
|
13
|
+
}(MeasurementState || {});
|
|
6
14
|
function useInterval(callback, delay) {
|
|
7
15
|
const savedCallback = useRef(callback);
|
|
8
16
|
useEffect(() => {
|
|
@@ -19,6 +27,7 @@ function useInterval(callback, delay) {
|
|
|
19
27
|
const VisibilitySensor = /*#__PURE__*/forwardRef((props, ref) => {
|
|
20
28
|
const {
|
|
21
29
|
onChange,
|
|
30
|
+
onPercentChange,
|
|
22
31
|
disabled = false,
|
|
23
32
|
triggerOnce = false,
|
|
24
33
|
delay,
|
|
@@ -26,10 +35,14 @@ const VisibilitySensor = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
26
35
|
children,
|
|
27
36
|
...rest
|
|
28
37
|
} = props;
|
|
29
|
-
const localRef = useRef(null);
|
|
30
38
|
useImperativeHandle(ref, () => ({
|
|
31
39
|
getInnerRef: () => localRef.current
|
|
32
40
|
}));
|
|
41
|
+
const window = useWindowDimensions();
|
|
42
|
+
const localRef = useRef(null);
|
|
43
|
+
const isMountedRef = useRef(true);
|
|
44
|
+
const measurementStateRef = useRef(MeasurementState.IDLE);
|
|
45
|
+
const lastPercentRef = useRef(undefined);
|
|
33
46
|
const [rectDimensions, setRectDimensions] = useState({
|
|
34
47
|
rectTop: 0,
|
|
35
48
|
rectBottom: 0,
|
|
@@ -40,12 +53,18 @@ const VisibilitySensor = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
40
53
|
});
|
|
41
54
|
const [lastValue, setLastValue] = useState(undefined);
|
|
42
55
|
const [active, setActive] = useState(false);
|
|
43
|
-
const
|
|
44
|
-
const measureInnerView = () => {
|
|
56
|
+
const measureInnerView = useCallback(() => {
|
|
45
57
|
/* Check if the sensor is active to prevent unnecessary measurements
|
|
46
58
|
This avoids running measurements when the sensor is disabled or stopped */
|
|
47
|
-
if (!active)
|
|
59
|
+
if (!active || !isMountedRef.current || measurementStateRef.current === MeasurementState.MEASURING) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
measurementStateRef.current = MeasurementState.MEASURING;
|
|
48
63
|
localRef.current?.measure((_x, _y, width, height, pageX, pageY) => {
|
|
64
|
+
// Check if component is still mounted before setting state because measurement can be asynchronous
|
|
65
|
+
if (!isMountedRef.current) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
49
68
|
const dimensions = {
|
|
50
69
|
rectTop: pageY,
|
|
51
70
|
rectBottom: pageY + height,
|
|
@@ -56,12 +75,13 @@ const VisibilitySensor = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
56
75
|
};
|
|
57
76
|
if (rectDimensions.rectTop !== dimensions.rectTop || rectDimensions.rectBottom !== dimensions.rectBottom || rectDimensions.rectLeft !== dimensions.rectLeft || rectDimensions.rectRight !== dimensions.rectRight || rectDimensions.rectWidth !== dimensions.rectWidth || rectDimensions.rectHeight !== dimensions.rectHeight) {
|
|
58
77
|
setRectDimensions(dimensions);
|
|
59
|
-
/* Set hasMeasuredRef to true to indicate that a valid measurement has been taken
|
|
60
|
-
This ensures visibility checks only proceed after initial measurement */
|
|
61
|
-
hasMeasuredRef.current = true;
|
|
62
78
|
}
|
|
79
|
+
|
|
80
|
+
/* Set measurementStateRef to MEASURED to indicate that a valid measurement has
|
|
81
|
+
been taken. This ensures visibility checks only proceed after initial measurement */
|
|
82
|
+
measurementStateRef.current = MeasurementState.MEASURED;
|
|
63
83
|
});
|
|
64
|
-
};
|
|
84
|
+
}, [active, rectDimensions]);
|
|
65
85
|
useInterval(measureInnerView, delay || 100);
|
|
66
86
|
const startWatching = useCallback(() => {
|
|
67
87
|
if (!active) setActive(true);
|
|
@@ -71,9 +91,37 @@ const VisibilitySensor = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
71
91
|
setActive(false);
|
|
72
92
|
/* Reset measurement state when stopping to ensure fresh measurements
|
|
73
93
|
when the sensor is reactivated */
|
|
74
|
-
|
|
94
|
+
measurementStateRef.current = MeasurementState.IDLE; // Reset state
|
|
75
95
|
}
|
|
76
96
|
}, [active]);
|
|
97
|
+
|
|
98
|
+
// Effect to trigger initial measurement when component becomes active:
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
let timer;
|
|
101
|
+
if (active && measurementStateRef.current === MeasurementState.IDLE) {
|
|
102
|
+
// Use setTimeout with 0 delay to ensure layout is complete
|
|
103
|
+
timer = setTimeout(() => {
|
|
104
|
+
measureInnerView();
|
|
105
|
+
}, 0);
|
|
106
|
+
}
|
|
107
|
+
return () => {
|
|
108
|
+
if (timer) clearTimeout(timer);
|
|
109
|
+
};
|
|
110
|
+
}, [active, measureInnerView]);
|
|
111
|
+
|
|
112
|
+
// Reset measurement state when dimensions change:
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (isMountedRef.current && measurementStateRef.current === MeasurementState.MEASURED) {
|
|
115
|
+
// Reset measurement state to force remeasurement with new dimensions
|
|
116
|
+
measurementStateRef.current = MeasurementState.IDLE;
|
|
117
|
+
}
|
|
118
|
+
}, [window]);
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
isMountedRef.current = true;
|
|
121
|
+
return () => {
|
|
122
|
+
isMountedRef.current = false;
|
|
123
|
+
};
|
|
124
|
+
}, []);
|
|
77
125
|
useEffect(() => {
|
|
78
126
|
if (!disabled) {
|
|
79
127
|
startWatching();
|
|
@@ -86,8 +134,9 @@ const VisibilitySensor = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
86
134
|
/* Ensure visibility checks only run when the sensor is active and
|
|
87
135
|
at least one measurement has been completed. This prevents
|
|
88
136
|
premature visibility calculations with invalid or stale dimensions */
|
|
89
|
-
if (!active || !
|
|
90
|
-
|
|
137
|
+
if (!active || measurementStateRef.current !== MeasurementState.MEASURED || !isMountedRef.current) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
91
140
|
const isVisible = rectDimensions.rectTop + (threshold.top || 0) <= window.height &&
|
|
92
141
|
// Top edge is within the bottom of the window
|
|
93
142
|
rectDimensions.rectBottom - (threshold.bottom || 0) >= 0 &&
|
|
@@ -96,6 +145,39 @@ const VisibilitySensor = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
96
145
|
// Left edge is within the right of the window
|
|
97
146
|
rectDimensions.rectRight - (threshold.right || 0) >= 0; // Right edge is within the left of the window
|
|
98
147
|
|
|
148
|
+
// Calculate percent visible if callback is requested / provided
|
|
149
|
+
if (onPercentChange && rectDimensions.rectWidth > 0 && rectDimensions.rectHeight > 0 && isVisible // Don't perform % calculation if not visible for efficiency
|
|
150
|
+
) {
|
|
151
|
+
// Thresholds reduce the effective viewport
|
|
152
|
+
const viewportTop = 0 + (threshold.top || 0);
|
|
153
|
+
const viewportBottom = window.height - (threshold.bottom || 0);
|
|
154
|
+
const viewportLeft = 0 + (threshold.left || 0);
|
|
155
|
+
const viewportRight = window.width - (threshold.right || 0);
|
|
156
|
+
|
|
157
|
+
// Calculate the visible portion of the element within the reduced viewport
|
|
158
|
+
const visibleTop = Math.max(viewportTop, rectDimensions.rectTop);
|
|
159
|
+
const visibleBottom = Math.min(viewportBottom, rectDimensions.rectBottom);
|
|
160
|
+
const visibleLeft = Math.max(viewportLeft, rectDimensions.rectLeft);
|
|
161
|
+
const visibleRight = Math.min(viewportRight, rectDimensions.rectRight);
|
|
162
|
+
|
|
163
|
+
// Calculate visible dimensions
|
|
164
|
+
const visibleHeight = Math.max(0, visibleBottom - visibleTop);
|
|
165
|
+
const visibleWidth = Math.max(0, visibleRight - visibleLeft);
|
|
166
|
+
|
|
167
|
+
// Calculate percent visible based on actual element area
|
|
168
|
+
const visibleArea = visibleHeight * visibleWidth;
|
|
169
|
+
const totalArea = rectDimensions.rectHeight * rectDimensions.rectWidth;
|
|
170
|
+
const percentVisible = totalArea > 0 ? Math.round(visibleArea / totalArea * 100) : 0;
|
|
171
|
+
|
|
172
|
+
// Only fire callback if percent has changed
|
|
173
|
+
if (lastPercentRef.current !== percentVisible) {
|
|
174
|
+
lastPercentRef.current = percentVisible;
|
|
175
|
+
onPercentChange(percentVisible);
|
|
176
|
+
}
|
|
177
|
+
} else if (onPercentChange && rectDimensions.rectWidth > 0 && rectDimensions.rectHeight > 0 && !isVisible // If not visible, always report 0%
|
|
178
|
+
) {
|
|
179
|
+
onPercentChange(0);
|
|
180
|
+
}
|
|
99
181
|
if (lastValue !== isVisible) {
|
|
100
182
|
setLastValue(isVisible);
|
|
101
183
|
onChange(isVisible);
|
|
@@ -103,8 +185,7 @@ const VisibilitySensor = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
103
185
|
stopWatching();
|
|
104
186
|
}
|
|
105
187
|
}
|
|
106
|
-
|
|
107
|
-
}, [rectDimensions, lastValue, active]);
|
|
188
|
+
}, [rectDimensions, window, lastValue, active, onPercentChange, threshold, onChange, triggerOnce, stopWatching]);
|
|
108
189
|
return /*#__PURE__*/_jsx(View, {
|
|
109
190
|
ref: localRef,
|
|
110
191
|
...rest,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","useCallback","useEffect","useRef","useState","forwardRef","useImperativeHandle","
|
|
1
|
+
{"version":3,"names":["React","useCallback","useEffect","useRef","useState","forwardRef","useImperativeHandle","useWindowDimensions","View","jsx","_jsx","MeasurementState","useInterval","callback","delay","savedCallback","current","undefined","id","setInterval","clearInterval","VisibilitySensor","props","ref","onChange","onPercentChange","disabled","triggerOnce","threshold","children","rest","getInnerRef","localRef","window","isMountedRef","measurementStateRef","IDLE","lastPercentRef","rectDimensions","setRectDimensions","rectTop","rectBottom","rectLeft","rectRight","rectWidth","rectHeight","lastValue","setLastValue","active","setActive","measureInnerView","MEASURING","measure","_x","_y","width","height","pageX","pageY","dimensions","MEASURED","startWatching","stopWatching","timer","setTimeout","clearTimeout","isVisible","top","bottom","left","right","viewportTop","viewportBottom","viewportLeft","viewportRight","visibleTop","Math","max","visibleBottom","min","visibleLeft","visibleRight","visibleHeight","visibleWidth","visibleArea","totalArea","percentVisible","round"],"sourceRoot":"../../src","sources":["VisibilitySensor.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VisibilitySensor.d.ts","sourceRoot":"","sources":["../../src/VisibilitySensor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAON,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EACV,mBAAmB,EACnB,qBAAqB,EAEtB,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"VisibilitySensor.d.ts","sourceRoot":"","sources":["../../src/VisibilitySensor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAON,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EACV,mBAAmB,EACnB,qBAAqB,EAEtB,MAAM,0BAA0B,CAAC;AAyBlC,QAAA,MAAM,gBAAgB,mGA4OrB,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { View, ViewProps } from 'react-native';
|
|
2
2
|
export interface VisibilitySensorProps extends ViewProps {
|
|
3
3
|
onChange: (visible: boolean) => void;
|
|
4
|
+
onPercentChange?: (percentVisible: number) => void;
|
|
4
5
|
disabled?: boolean;
|
|
5
6
|
triggerOnce?: boolean;
|
|
6
7
|
delay?: number | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"visibilitySensor.types.d.ts","sourceRoot":"","sources":["../../src/visibilitySensor.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,WAAW,qBAAsB,SAAQ,SAAS;IACtD,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,yBAAyB,CAAC;CACvC;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,yBAAyB;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB"}
|
|
1
|
+
{"version":3,"file":"visibilitySensor.types.d.ts","sourceRoot":"","sources":["../../src/visibilitySensor.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,WAAW,qBAAsB,SAAQ,SAAS;IACtD,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC,eAAe,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,yBAAyB,CAAC;CACvC;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,yBAAyB;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@futurejj/react-native-visibility-sensor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0-beta.2",
|
|
4
4
|
"description": "A React Native wrapper to check whether a component is in the view port to track impressions and clicks",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
package/src/VisibilitySensor.tsx
CHANGED
|
@@ -6,13 +6,19 @@ import React, {
|
|
|
6
6
|
forwardRef,
|
|
7
7
|
useImperativeHandle,
|
|
8
8
|
} from 'react';
|
|
9
|
-
import {
|
|
9
|
+
import { useWindowDimensions, View } from 'react-native';
|
|
10
10
|
import type {
|
|
11
11
|
VisibilitySensorRef,
|
|
12
12
|
VisibilitySensorProps,
|
|
13
13
|
RectDimensionsState,
|
|
14
14
|
} from './visibilitySensor.types';
|
|
15
15
|
|
|
16
|
+
enum MeasurementState {
|
|
17
|
+
IDLE = 'IDLE', // Not yet measured
|
|
18
|
+
MEASURING = 'MEASURING', // Measurement in progress
|
|
19
|
+
MEASURED = 'MEASURED', // Has valid measurements
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
function useInterval(callback: () => void, delay: number | null) {
|
|
17
23
|
const savedCallback = useRef(callback);
|
|
18
24
|
|
|
@@ -34,6 +40,7 @@ const VisibilitySensor = forwardRef<VisibilitySensorRef, VisibilitySensorProps>(
|
|
|
34
40
|
(props, ref) => {
|
|
35
41
|
const {
|
|
36
42
|
onChange,
|
|
43
|
+
onPercentChange,
|
|
37
44
|
disabled = false,
|
|
38
45
|
triggerOnce = false,
|
|
39
46
|
delay,
|
|
@@ -42,12 +49,17 @@ const VisibilitySensor = forwardRef<VisibilitySensorRef, VisibilitySensorProps>(
|
|
|
42
49
|
...rest
|
|
43
50
|
} = props;
|
|
44
51
|
|
|
45
|
-
const localRef = useRef<View>(null);
|
|
46
|
-
|
|
47
52
|
useImperativeHandle(ref, () => ({
|
|
48
53
|
getInnerRef: () => localRef.current,
|
|
49
54
|
}));
|
|
50
55
|
|
|
56
|
+
const window = useWindowDimensions();
|
|
57
|
+
|
|
58
|
+
const localRef = useRef<View>(null);
|
|
59
|
+
const isMountedRef = useRef(true);
|
|
60
|
+
const measurementStateRef = useRef<MeasurementState>(MeasurementState.IDLE);
|
|
61
|
+
const lastPercentRef = useRef<number | undefined>(undefined);
|
|
62
|
+
|
|
51
63
|
const [rectDimensions, setRectDimensions] = useState<RectDimensionsState>({
|
|
52
64
|
rectTop: 0,
|
|
53
65
|
rectBottom: 0,
|
|
@@ -58,12 +70,19 @@ const VisibilitySensor = forwardRef<VisibilitySensorRef, VisibilitySensorProps>(
|
|
|
58
70
|
});
|
|
59
71
|
const [lastValue, setLastValue] = useState<boolean | undefined>(undefined);
|
|
60
72
|
const [active, setActive] = useState<boolean>(false);
|
|
61
|
-
const hasMeasuredRef = useRef(false);
|
|
62
73
|
|
|
63
|
-
const measureInnerView = () => {
|
|
74
|
+
const measureInnerView = useCallback(() => {
|
|
64
75
|
/* Check if the sensor is active to prevent unnecessary measurements
|
|
65
76
|
This avoids running measurements when the sensor is disabled or stopped */
|
|
66
|
-
if (
|
|
77
|
+
if (
|
|
78
|
+
!active ||
|
|
79
|
+
!isMountedRef.current ||
|
|
80
|
+
measurementStateRef.current === MeasurementState.MEASURING
|
|
81
|
+
) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
measurementStateRef.current = MeasurementState.MEASURING;
|
|
67
86
|
|
|
68
87
|
localRef.current?.measure(
|
|
69
88
|
(
|
|
@@ -74,6 +93,11 @@ const VisibilitySensor = forwardRef<VisibilitySensorRef, VisibilitySensorProps>(
|
|
|
74
93
|
pageX: number,
|
|
75
94
|
pageY: number
|
|
76
95
|
) => {
|
|
96
|
+
// Check if component is still mounted before setting state because measurement can be asynchronous
|
|
97
|
+
if (!isMountedRef.current) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
77
101
|
const dimensions = {
|
|
78
102
|
rectTop: pageY,
|
|
79
103
|
rectBottom: pageY + height,
|
|
@@ -91,13 +115,14 @@ const VisibilitySensor = forwardRef<VisibilitySensorRef, VisibilitySensorProps>(
|
|
|
91
115
|
rectDimensions.rectHeight !== dimensions.rectHeight
|
|
92
116
|
) {
|
|
93
117
|
setRectDimensions(dimensions);
|
|
94
|
-
/* Set hasMeasuredRef to true to indicate that a valid measurement has been taken
|
|
95
|
-
This ensures visibility checks only proceed after initial measurement */
|
|
96
|
-
hasMeasuredRef.current = true;
|
|
97
118
|
}
|
|
119
|
+
|
|
120
|
+
/* Set measurementStateRef to MEASURED to indicate that a valid measurement has
|
|
121
|
+
been taken. This ensures visibility checks only proceed after initial measurement */
|
|
122
|
+
measurementStateRef.current = MeasurementState.MEASURED;
|
|
98
123
|
}
|
|
99
124
|
);
|
|
100
|
-
};
|
|
125
|
+
}, [active, rectDimensions]);
|
|
101
126
|
|
|
102
127
|
useInterval(measureInnerView, delay || 100);
|
|
103
128
|
|
|
@@ -110,10 +135,44 @@ const VisibilitySensor = forwardRef<VisibilitySensorRef, VisibilitySensorProps>(
|
|
|
110
135
|
setActive(false);
|
|
111
136
|
/* Reset measurement state when stopping to ensure fresh measurements
|
|
112
137
|
when the sensor is reactivated */
|
|
113
|
-
|
|
138
|
+
measurementStateRef.current = MeasurementState.IDLE; // Reset state
|
|
114
139
|
}
|
|
115
140
|
}, [active]);
|
|
116
141
|
|
|
142
|
+
// Effect to trigger initial measurement when component becomes active:
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
145
|
+
|
|
146
|
+
if (active && measurementStateRef.current === MeasurementState.IDLE) {
|
|
147
|
+
// Use setTimeout with 0 delay to ensure layout is complete
|
|
148
|
+
timer = setTimeout(() => {
|
|
149
|
+
measureInnerView();
|
|
150
|
+
}, 0);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return () => {
|
|
154
|
+
if (timer) clearTimeout(timer);
|
|
155
|
+
};
|
|
156
|
+
}, [active, measureInnerView]);
|
|
157
|
+
|
|
158
|
+
// Reset measurement state when dimensions change:
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
if (
|
|
161
|
+
isMountedRef.current &&
|
|
162
|
+
measurementStateRef.current === MeasurementState.MEASURED
|
|
163
|
+
) {
|
|
164
|
+
// Reset measurement state to force remeasurement with new dimensions
|
|
165
|
+
measurementStateRef.current = MeasurementState.IDLE;
|
|
166
|
+
}
|
|
167
|
+
}, [window]);
|
|
168
|
+
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
isMountedRef.current = true;
|
|
171
|
+
return () => {
|
|
172
|
+
isMountedRef.current = false;
|
|
173
|
+
};
|
|
174
|
+
}, []);
|
|
175
|
+
|
|
117
176
|
useEffect(() => {
|
|
118
177
|
if (!disabled) {
|
|
119
178
|
startWatching();
|
|
@@ -128,15 +187,66 @@ const VisibilitySensor = forwardRef<VisibilitySensorRef, VisibilitySensorProps>(
|
|
|
128
187
|
/* Ensure visibility checks only run when the sensor is active and
|
|
129
188
|
at least one measurement has been completed. This prevents
|
|
130
189
|
premature visibility calculations with invalid or stale dimensions */
|
|
131
|
-
if (
|
|
190
|
+
if (
|
|
191
|
+
!active ||
|
|
192
|
+
measurementStateRef.current !== MeasurementState.MEASURED ||
|
|
193
|
+
!isMountedRef.current
|
|
194
|
+
) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
132
197
|
|
|
133
|
-
const window: ScaledSize = Dimensions.get('window');
|
|
134
198
|
const isVisible: boolean =
|
|
135
199
|
rectDimensions.rectTop + (threshold.top || 0) <= window.height && // Top edge is within the bottom of the window
|
|
136
200
|
rectDimensions.rectBottom - (threshold.bottom || 0) >= 0 && // Bottom edge is within the top of the window
|
|
137
201
|
rectDimensions.rectLeft + (threshold.left || 0) <= window.width && // Left edge is within the right of the window
|
|
138
202
|
rectDimensions.rectRight - (threshold.right || 0) >= 0; // Right edge is within the left of the window
|
|
139
203
|
|
|
204
|
+
// Calculate percent visible if callback is requested / provided
|
|
205
|
+
if (
|
|
206
|
+
onPercentChange &&
|
|
207
|
+
rectDimensions.rectWidth > 0 &&
|
|
208
|
+
rectDimensions.rectHeight > 0 &&
|
|
209
|
+
isVisible // Don't perform % calculation if not visible for efficiency
|
|
210
|
+
) {
|
|
211
|
+
// Thresholds reduce the effective viewport
|
|
212
|
+
const viewportTop = 0 + (threshold.top || 0);
|
|
213
|
+
const viewportBottom = window.height - (threshold.bottom || 0);
|
|
214
|
+
const viewportLeft = 0 + (threshold.left || 0);
|
|
215
|
+
const viewportRight = window.width - (threshold.right || 0);
|
|
216
|
+
|
|
217
|
+
// Calculate the visible portion of the element within the reduced viewport
|
|
218
|
+
const visibleTop = Math.max(viewportTop, rectDimensions.rectTop);
|
|
219
|
+
const visibleBottom = Math.min(
|
|
220
|
+
viewportBottom,
|
|
221
|
+
rectDimensions.rectBottom
|
|
222
|
+
);
|
|
223
|
+
const visibleLeft = Math.max(viewportLeft, rectDimensions.rectLeft);
|
|
224
|
+
const visibleRight = Math.min(viewportRight, rectDimensions.rectRight);
|
|
225
|
+
|
|
226
|
+
// Calculate visible dimensions
|
|
227
|
+
const visibleHeight = Math.max(0, visibleBottom - visibleTop);
|
|
228
|
+
const visibleWidth = Math.max(0, visibleRight - visibleLeft);
|
|
229
|
+
|
|
230
|
+
// Calculate percent visible based on actual element area
|
|
231
|
+
const visibleArea = visibleHeight * visibleWidth;
|
|
232
|
+
const totalArea = rectDimensions.rectHeight * rectDimensions.rectWidth;
|
|
233
|
+
const percentVisible =
|
|
234
|
+
totalArea > 0 ? Math.round((visibleArea / totalArea) * 100) : 0;
|
|
235
|
+
|
|
236
|
+
// Only fire callback if percent has changed
|
|
237
|
+
if (lastPercentRef.current !== percentVisible) {
|
|
238
|
+
lastPercentRef.current = percentVisible;
|
|
239
|
+
onPercentChange(percentVisible);
|
|
240
|
+
}
|
|
241
|
+
} else if (
|
|
242
|
+
onPercentChange &&
|
|
243
|
+
rectDimensions.rectWidth > 0 &&
|
|
244
|
+
rectDimensions.rectHeight > 0 &&
|
|
245
|
+
!isVisible // If not visible, always report 0%
|
|
246
|
+
) {
|
|
247
|
+
onPercentChange(0);
|
|
248
|
+
}
|
|
249
|
+
|
|
140
250
|
if (lastValue !== isVisible) {
|
|
141
251
|
setLastValue(isVisible);
|
|
142
252
|
onChange(isVisible);
|
|
@@ -144,8 +254,17 @@ const VisibilitySensor = forwardRef<VisibilitySensorRef, VisibilitySensorProps>(
|
|
|
144
254
|
stopWatching();
|
|
145
255
|
}
|
|
146
256
|
}
|
|
147
|
-
|
|
148
|
-
|
|
257
|
+
}, [
|
|
258
|
+
rectDimensions,
|
|
259
|
+
window,
|
|
260
|
+
lastValue,
|
|
261
|
+
active,
|
|
262
|
+
onPercentChange,
|
|
263
|
+
threshold,
|
|
264
|
+
onChange,
|
|
265
|
+
triggerOnce,
|
|
266
|
+
stopWatching,
|
|
267
|
+
]);
|
|
149
268
|
|
|
150
269
|
return (
|
|
151
270
|
<View ref={localRef} {...rest}>
|
|
@@ -2,6 +2,7 @@ import type { View, ViewProps } from 'react-native';
|
|
|
2
2
|
|
|
3
3
|
export interface VisibilitySensorProps extends ViewProps {
|
|
4
4
|
onChange: (visible: boolean) => void;
|
|
5
|
+
onPercentChange?: (percentVisible: number) => void;
|
|
5
6
|
disabled?: boolean;
|
|
6
7
|
triggerOnce?: boolean;
|
|
7
8
|
delay?: number | undefined;
|