@usefy/use-intersection-observer 0.0.1
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 +514 -0
- package/dist/index.d.mts +321 -0
- package/dist/index.d.ts +321 -0
- package/dist/index.js +235 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +205 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +62 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extended intersection entry with convenience properties
|
|
3
|
+
* Wraps the native IntersectionObserverEntry with additional computed values
|
|
4
|
+
*/
|
|
5
|
+
interface IntersectionEntry {
|
|
6
|
+
/**
|
|
7
|
+
* The original native IntersectionObserverEntry
|
|
8
|
+
*/
|
|
9
|
+
readonly entry: IntersectionObserverEntry;
|
|
10
|
+
/**
|
|
11
|
+
* Whether the target element is currently intersecting with the root
|
|
12
|
+
*/
|
|
13
|
+
readonly isIntersecting: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The ratio of the target element that is visible (0.0 to 1.0)
|
|
16
|
+
*/
|
|
17
|
+
readonly intersectionRatio: number;
|
|
18
|
+
/**
|
|
19
|
+
* The target element being observed
|
|
20
|
+
*/
|
|
21
|
+
readonly target: Element;
|
|
22
|
+
/**
|
|
23
|
+
* The bounding rectangle of the target element
|
|
24
|
+
*/
|
|
25
|
+
readonly boundingClientRect: DOMRectReadOnly;
|
|
26
|
+
/**
|
|
27
|
+
* The visible portion of the target element (intersection area)
|
|
28
|
+
*/
|
|
29
|
+
readonly intersectionRect: DOMRectReadOnly;
|
|
30
|
+
/**
|
|
31
|
+
* The bounding rectangle of the root element (viewport if null)
|
|
32
|
+
*/
|
|
33
|
+
readonly rootBounds: DOMRectReadOnly | null;
|
|
34
|
+
/**
|
|
35
|
+
* Timestamp when the intersection was recorded
|
|
36
|
+
*/
|
|
37
|
+
readonly time: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Callback function called when intersection state changes
|
|
41
|
+
* @param entry - The intersection entry data
|
|
42
|
+
* @param inView - Whether the element is currently in view
|
|
43
|
+
*/
|
|
44
|
+
type OnChangeCallback = (entry: IntersectionEntry, inView: boolean) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Options for configuring the useIntersectionObserver hook
|
|
47
|
+
*/
|
|
48
|
+
interface UseIntersectionObserverOptions {
|
|
49
|
+
/**
|
|
50
|
+
* Threshold(s) at which the callback is triggered
|
|
51
|
+
* A single number or array of numbers between 0.0 and 1.0
|
|
52
|
+
* - 0: Triggers as soon as any pixel is visible
|
|
53
|
+
* - 0.5: Triggers when 50% of the element is visible
|
|
54
|
+
* - 1.0: Triggers when the entire element is visible
|
|
55
|
+
* @default 0
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```tsx
|
|
59
|
+
* // Single threshold
|
|
60
|
+
* useIntersectionObserver({ threshold: 0.5 })
|
|
61
|
+
*
|
|
62
|
+
* // Multiple thresholds for granular tracking
|
|
63
|
+
* useIntersectionObserver({ threshold: [0, 0.25, 0.5, 0.75, 1] })
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
threshold?: number | number[];
|
|
67
|
+
/**
|
|
68
|
+
* The root element used as the viewport for checking visibility
|
|
69
|
+
* If null (default), uses the browser viewport
|
|
70
|
+
* @default null
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```tsx
|
|
74
|
+
* // Use a custom scrollable container
|
|
75
|
+
* const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
76
|
+
* useIntersectionObserver({ root: scrollContainerRef.current })
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
root?: Element | Document | null;
|
|
80
|
+
/**
|
|
81
|
+
* Margin around the root element (CSS margin syntax)
|
|
82
|
+
* Can be used to grow or shrink the root's bounding box before computing intersections
|
|
83
|
+
* Values can be pixels (px) or percentages (%)
|
|
84
|
+
* @default "0px"
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```tsx
|
|
88
|
+
* // Trigger 100px before element enters viewport
|
|
89
|
+
* useIntersectionObserver({ rootMargin: "100px 0px" })
|
|
90
|
+
*
|
|
91
|
+
* // Trigger when element is 20% away from viewport
|
|
92
|
+
* useIntersectionObserver({ rootMargin: "20%" })
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
rootMargin?: string;
|
|
96
|
+
/**
|
|
97
|
+
* Stop observing after the element first becomes visible
|
|
98
|
+
* Useful for lazy loading images or one-time animations
|
|
99
|
+
* @default false
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```tsx
|
|
103
|
+
* // Load image only once when it enters viewport
|
|
104
|
+
* const { ref, inView } = useIntersectionObserver({ triggerOnce: true });
|
|
105
|
+
* return <img ref={ref} src={inView ? imageSrc : placeholder} />;
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
triggerOnce?: boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Enable or disable the observer dynamically
|
|
111
|
+
* When false, the observer is disconnected and no callbacks are fired
|
|
112
|
+
* @default true
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```tsx
|
|
116
|
+
* // Disable observation while loading
|
|
117
|
+
* const { ref } = useIntersectionObserver({ enabled: !isLoading });
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
enabled?: boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Initial intersection state before the first observation
|
|
123
|
+
* Useful for SSR/SSG scenarios where you want to assume a state
|
|
124
|
+
* @default false
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```tsx
|
|
128
|
+
* // Assume above-the-fold content is visible initially
|
|
129
|
+
* useIntersectionObserver({ initialIsIntersecting: true })
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
initialIsIntersecting?: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Callback fired when intersection state changes
|
|
135
|
+
* Called with the intersection entry and a boolean indicating if in view
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```tsx
|
|
139
|
+
* useIntersectionObserver({
|
|
140
|
+
* onChange: (entry, inView) => {
|
|
141
|
+
* if (inView) {
|
|
142
|
+
* analytics.track('element_viewed', { ratio: entry.intersectionRatio });
|
|
143
|
+
* }
|
|
144
|
+
* }
|
|
145
|
+
* })
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
onChange?: OnChangeCallback;
|
|
149
|
+
/**
|
|
150
|
+
* Delay in milliseconds before starting to observe
|
|
151
|
+
* Useful for preventing flash of content on fast scrolling
|
|
152
|
+
* @default 0
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```tsx
|
|
156
|
+
* // Wait 100ms before starting observation
|
|
157
|
+
* useIntersectionObserver({ delay: 100 })
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
delay?: number;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Return type for the useIntersectionObserver hook
|
|
164
|
+
*/
|
|
165
|
+
interface UseIntersectionObserverReturn {
|
|
166
|
+
/**
|
|
167
|
+
* The current intersection entry data
|
|
168
|
+
* null if element hasn't been observed yet or if SSR
|
|
169
|
+
*/
|
|
170
|
+
entry: IntersectionEntry | null;
|
|
171
|
+
/**
|
|
172
|
+
* Boolean indicating whether the element is currently in view
|
|
173
|
+
* Shorthand for entry?.isIntersecting ?? initialIsIntersecting
|
|
174
|
+
*/
|
|
175
|
+
inView: boolean;
|
|
176
|
+
/**
|
|
177
|
+
* Callback ref to attach to the target element
|
|
178
|
+
* Can be passed directly to the ref prop of any element
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```tsx
|
|
182
|
+
* const { ref, inView } = useIntersectionObserver();
|
|
183
|
+
* return <div ref={ref}>{inView ? 'Visible!' : 'Not visible'}</div>;
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
ref: (node: Element | null) => void;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* A React hook for observing element visibility using the Intersection Observer API.
|
|
191
|
+
*
|
|
192
|
+
* Features:
|
|
193
|
+
* - Efficient viewport/container visibility detection
|
|
194
|
+
* - Threshold-based intersection callbacks
|
|
195
|
+
* - TriggerOnce support for lazy loading patterns
|
|
196
|
+
* - Dynamic enable/disable support
|
|
197
|
+
* - SSR compatible with graceful degradation
|
|
198
|
+
* - TypeScript support with full type inference
|
|
199
|
+
*
|
|
200
|
+
* @param options - Configuration options for the observer
|
|
201
|
+
* @returns Object containing entry data, inView boolean, and ref callback
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* ```tsx
|
|
205
|
+
* // Basic usage - check if element is visible
|
|
206
|
+
* function Component() {
|
|
207
|
+
* const { ref, inView } = useIntersectionObserver();
|
|
208
|
+
* return (
|
|
209
|
+
* <div ref={ref}>
|
|
210
|
+
* {inView ? 'Visible!' : 'Not visible'}
|
|
211
|
+
* </div>
|
|
212
|
+
* );
|
|
213
|
+
* }
|
|
214
|
+
* ```
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```tsx
|
|
218
|
+
* // Lazy load image when it enters viewport
|
|
219
|
+
* function LazyImage({ src, alt }: { src: string; alt: string }) {
|
|
220
|
+
* const { ref, inView } = useIntersectionObserver({
|
|
221
|
+
* triggerOnce: true,
|
|
222
|
+
* threshold: 0.1,
|
|
223
|
+
* });
|
|
224
|
+
*
|
|
225
|
+
* return (
|
|
226
|
+
* <div ref={ref}>
|
|
227
|
+
* {inView ? (
|
|
228
|
+
* <img src={src} alt={alt} />
|
|
229
|
+
* ) : (
|
|
230
|
+
* <div className="placeholder" />
|
|
231
|
+
* )}
|
|
232
|
+
* </div>
|
|
233
|
+
* );
|
|
234
|
+
* }
|
|
235
|
+
* ```
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```tsx
|
|
239
|
+
* // Infinite scroll with sentinel element
|
|
240
|
+
* function InfiniteList() {
|
|
241
|
+
* const { ref, inView } = useIntersectionObserver({
|
|
242
|
+
* threshold: 1.0,
|
|
243
|
+
* rootMargin: '100px',
|
|
244
|
+
* });
|
|
245
|
+
*
|
|
246
|
+
* useEffect(() => {
|
|
247
|
+
* if (inView) {
|
|
248
|
+
* loadMoreItems();
|
|
249
|
+
* }
|
|
250
|
+
* }, [inView]);
|
|
251
|
+
*
|
|
252
|
+
* return (
|
|
253
|
+
* <div>
|
|
254
|
+
* {items.map(item => <Item key={item.id} {...item} />)}
|
|
255
|
+
* <div ref={ref} />
|
|
256
|
+
* </div>
|
|
257
|
+
* );
|
|
258
|
+
* }
|
|
259
|
+
* ```
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* ```tsx
|
|
263
|
+
* // Track scroll progress with multiple thresholds
|
|
264
|
+
* function ProgressTracker() {
|
|
265
|
+
* const { ref, entry } = useIntersectionObserver({
|
|
266
|
+
* threshold: [0, 0.25, 0.5, 0.75, 1.0],
|
|
267
|
+
* onChange: (entry, inView) => {
|
|
268
|
+
* console.log('Progress:', Math.round(entry.intersectionRatio * 100), '%');
|
|
269
|
+
* },
|
|
270
|
+
* });
|
|
271
|
+
*
|
|
272
|
+
* return <div ref={ref}>Long content...</div>;
|
|
273
|
+
* }
|
|
274
|
+
* ```
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```tsx
|
|
278
|
+
* // Custom scroll container as root
|
|
279
|
+
* function ScrollContainer() {
|
|
280
|
+
* const containerRef = useRef<HTMLDivElement>(null);
|
|
281
|
+
* const { ref, inView } = useIntersectionObserver({
|
|
282
|
+
* root: containerRef.current,
|
|
283
|
+
* rootMargin: '0px',
|
|
284
|
+
* });
|
|
285
|
+
*
|
|
286
|
+
* return (
|
|
287
|
+
* <div ref={containerRef} style={{ overflow: 'auto', height: 400 }}>
|
|
288
|
+
* <div style={{ height: 1000 }}>
|
|
289
|
+
* <div ref={ref}>{inView ? 'In container view' : 'Outside'}</div>
|
|
290
|
+
* </div>
|
|
291
|
+
* </div>
|
|
292
|
+
* );
|
|
293
|
+
* }
|
|
294
|
+
* ```
|
|
295
|
+
*/
|
|
296
|
+
declare function useIntersectionObserver(options?: UseIntersectionObserverOptions): UseIntersectionObserverReturn;
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Check if IntersectionObserver API is supported in the current environment
|
|
300
|
+
* Returns false in SSR environments or browsers without support
|
|
301
|
+
*/
|
|
302
|
+
declare function isIntersectionObserverSupported(): boolean;
|
|
303
|
+
/**
|
|
304
|
+
* Convert a native IntersectionObserverEntry to our IntersectionEntry type
|
|
305
|
+
* Provides a consistent interface with additional convenience properties
|
|
306
|
+
*
|
|
307
|
+
* @param nativeEntry - The native IntersectionObserverEntry from the browser
|
|
308
|
+
* @returns IntersectionEntry with all properties
|
|
309
|
+
*/
|
|
310
|
+
declare function toIntersectionEntry(nativeEntry: IntersectionObserverEntry): IntersectionEntry;
|
|
311
|
+
/**
|
|
312
|
+
* Create an initial IntersectionEntry for SSR or before first observation
|
|
313
|
+
* Used when initialIsIntersecting is true
|
|
314
|
+
*
|
|
315
|
+
* @param isIntersecting - Whether to set initial state as intersecting
|
|
316
|
+
* @param target - Optional target element (null for SSR)
|
|
317
|
+
* @returns A mock IntersectionEntry
|
|
318
|
+
*/
|
|
319
|
+
declare function createInitialEntry(isIntersecting: boolean, target?: Element | null): IntersectionEntry | null;
|
|
320
|
+
|
|
321
|
+
export { type IntersectionEntry, type OnChangeCallback, type UseIntersectionObserverOptions, type UseIntersectionObserverReturn, createInitialEntry, isIntersectionObserverSupported, toIntersectionEntry, useIntersectionObserver };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extended intersection entry with convenience properties
|
|
3
|
+
* Wraps the native IntersectionObserverEntry with additional computed values
|
|
4
|
+
*/
|
|
5
|
+
interface IntersectionEntry {
|
|
6
|
+
/**
|
|
7
|
+
* The original native IntersectionObserverEntry
|
|
8
|
+
*/
|
|
9
|
+
readonly entry: IntersectionObserverEntry;
|
|
10
|
+
/**
|
|
11
|
+
* Whether the target element is currently intersecting with the root
|
|
12
|
+
*/
|
|
13
|
+
readonly isIntersecting: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The ratio of the target element that is visible (0.0 to 1.0)
|
|
16
|
+
*/
|
|
17
|
+
readonly intersectionRatio: number;
|
|
18
|
+
/**
|
|
19
|
+
* The target element being observed
|
|
20
|
+
*/
|
|
21
|
+
readonly target: Element;
|
|
22
|
+
/**
|
|
23
|
+
* The bounding rectangle of the target element
|
|
24
|
+
*/
|
|
25
|
+
readonly boundingClientRect: DOMRectReadOnly;
|
|
26
|
+
/**
|
|
27
|
+
* The visible portion of the target element (intersection area)
|
|
28
|
+
*/
|
|
29
|
+
readonly intersectionRect: DOMRectReadOnly;
|
|
30
|
+
/**
|
|
31
|
+
* The bounding rectangle of the root element (viewport if null)
|
|
32
|
+
*/
|
|
33
|
+
readonly rootBounds: DOMRectReadOnly | null;
|
|
34
|
+
/**
|
|
35
|
+
* Timestamp when the intersection was recorded
|
|
36
|
+
*/
|
|
37
|
+
readonly time: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Callback function called when intersection state changes
|
|
41
|
+
* @param entry - The intersection entry data
|
|
42
|
+
* @param inView - Whether the element is currently in view
|
|
43
|
+
*/
|
|
44
|
+
type OnChangeCallback = (entry: IntersectionEntry, inView: boolean) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Options for configuring the useIntersectionObserver hook
|
|
47
|
+
*/
|
|
48
|
+
interface UseIntersectionObserverOptions {
|
|
49
|
+
/**
|
|
50
|
+
* Threshold(s) at which the callback is triggered
|
|
51
|
+
* A single number or array of numbers between 0.0 and 1.0
|
|
52
|
+
* - 0: Triggers as soon as any pixel is visible
|
|
53
|
+
* - 0.5: Triggers when 50% of the element is visible
|
|
54
|
+
* - 1.0: Triggers when the entire element is visible
|
|
55
|
+
* @default 0
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```tsx
|
|
59
|
+
* // Single threshold
|
|
60
|
+
* useIntersectionObserver({ threshold: 0.5 })
|
|
61
|
+
*
|
|
62
|
+
* // Multiple thresholds for granular tracking
|
|
63
|
+
* useIntersectionObserver({ threshold: [0, 0.25, 0.5, 0.75, 1] })
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
threshold?: number | number[];
|
|
67
|
+
/**
|
|
68
|
+
* The root element used as the viewport for checking visibility
|
|
69
|
+
* If null (default), uses the browser viewport
|
|
70
|
+
* @default null
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```tsx
|
|
74
|
+
* // Use a custom scrollable container
|
|
75
|
+
* const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
76
|
+
* useIntersectionObserver({ root: scrollContainerRef.current })
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
root?: Element | Document | null;
|
|
80
|
+
/**
|
|
81
|
+
* Margin around the root element (CSS margin syntax)
|
|
82
|
+
* Can be used to grow or shrink the root's bounding box before computing intersections
|
|
83
|
+
* Values can be pixels (px) or percentages (%)
|
|
84
|
+
* @default "0px"
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```tsx
|
|
88
|
+
* // Trigger 100px before element enters viewport
|
|
89
|
+
* useIntersectionObserver({ rootMargin: "100px 0px" })
|
|
90
|
+
*
|
|
91
|
+
* // Trigger when element is 20% away from viewport
|
|
92
|
+
* useIntersectionObserver({ rootMargin: "20%" })
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
rootMargin?: string;
|
|
96
|
+
/**
|
|
97
|
+
* Stop observing after the element first becomes visible
|
|
98
|
+
* Useful for lazy loading images or one-time animations
|
|
99
|
+
* @default false
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```tsx
|
|
103
|
+
* // Load image only once when it enters viewport
|
|
104
|
+
* const { ref, inView } = useIntersectionObserver({ triggerOnce: true });
|
|
105
|
+
* return <img ref={ref} src={inView ? imageSrc : placeholder} />;
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
triggerOnce?: boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Enable or disable the observer dynamically
|
|
111
|
+
* When false, the observer is disconnected and no callbacks are fired
|
|
112
|
+
* @default true
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```tsx
|
|
116
|
+
* // Disable observation while loading
|
|
117
|
+
* const { ref } = useIntersectionObserver({ enabled: !isLoading });
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
enabled?: boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Initial intersection state before the first observation
|
|
123
|
+
* Useful for SSR/SSG scenarios where you want to assume a state
|
|
124
|
+
* @default false
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```tsx
|
|
128
|
+
* // Assume above-the-fold content is visible initially
|
|
129
|
+
* useIntersectionObserver({ initialIsIntersecting: true })
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
initialIsIntersecting?: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Callback fired when intersection state changes
|
|
135
|
+
* Called with the intersection entry and a boolean indicating if in view
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```tsx
|
|
139
|
+
* useIntersectionObserver({
|
|
140
|
+
* onChange: (entry, inView) => {
|
|
141
|
+
* if (inView) {
|
|
142
|
+
* analytics.track('element_viewed', { ratio: entry.intersectionRatio });
|
|
143
|
+
* }
|
|
144
|
+
* }
|
|
145
|
+
* })
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
onChange?: OnChangeCallback;
|
|
149
|
+
/**
|
|
150
|
+
* Delay in milliseconds before starting to observe
|
|
151
|
+
* Useful for preventing flash of content on fast scrolling
|
|
152
|
+
* @default 0
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```tsx
|
|
156
|
+
* // Wait 100ms before starting observation
|
|
157
|
+
* useIntersectionObserver({ delay: 100 })
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
delay?: number;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Return type for the useIntersectionObserver hook
|
|
164
|
+
*/
|
|
165
|
+
interface UseIntersectionObserverReturn {
|
|
166
|
+
/**
|
|
167
|
+
* The current intersection entry data
|
|
168
|
+
* null if element hasn't been observed yet or if SSR
|
|
169
|
+
*/
|
|
170
|
+
entry: IntersectionEntry | null;
|
|
171
|
+
/**
|
|
172
|
+
* Boolean indicating whether the element is currently in view
|
|
173
|
+
* Shorthand for entry?.isIntersecting ?? initialIsIntersecting
|
|
174
|
+
*/
|
|
175
|
+
inView: boolean;
|
|
176
|
+
/**
|
|
177
|
+
* Callback ref to attach to the target element
|
|
178
|
+
* Can be passed directly to the ref prop of any element
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```tsx
|
|
182
|
+
* const { ref, inView } = useIntersectionObserver();
|
|
183
|
+
* return <div ref={ref}>{inView ? 'Visible!' : 'Not visible'}</div>;
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
ref: (node: Element | null) => void;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* A React hook for observing element visibility using the Intersection Observer API.
|
|
191
|
+
*
|
|
192
|
+
* Features:
|
|
193
|
+
* - Efficient viewport/container visibility detection
|
|
194
|
+
* - Threshold-based intersection callbacks
|
|
195
|
+
* - TriggerOnce support for lazy loading patterns
|
|
196
|
+
* - Dynamic enable/disable support
|
|
197
|
+
* - SSR compatible with graceful degradation
|
|
198
|
+
* - TypeScript support with full type inference
|
|
199
|
+
*
|
|
200
|
+
* @param options - Configuration options for the observer
|
|
201
|
+
* @returns Object containing entry data, inView boolean, and ref callback
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* ```tsx
|
|
205
|
+
* // Basic usage - check if element is visible
|
|
206
|
+
* function Component() {
|
|
207
|
+
* const { ref, inView } = useIntersectionObserver();
|
|
208
|
+
* return (
|
|
209
|
+
* <div ref={ref}>
|
|
210
|
+
* {inView ? 'Visible!' : 'Not visible'}
|
|
211
|
+
* </div>
|
|
212
|
+
* );
|
|
213
|
+
* }
|
|
214
|
+
* ```
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```tsx
|
|
218
|
+
* // Lazy load image when it enters viewport
|
|
219
|
+
* function LazyImage({ src, alt }: { src: string; alt: string }) {
|
|
220
|
+
* const { ref, inView } = useIntersectionObserver({
|
|
221
|
+
* triggerOnce: true,
|
|
222
|
+
* threshold: 0.1,
|
|
223
|
+
* });
|
|
224
|
+
*
|
|
225
|
+
* return (
|
|
226
|
+
* <div ref={ref}>
|
|
227
|
+
* {inView ? (
|
|
228
|
+
* <img src={src} alt={alt} />
|
|
229
|
+
* ) : (
|
|
230
|
+
* <div className="placeholder" />
|
|
231
|
+
* )}
|
|
232
|
+
* </div>
|
|
233
|
+
* );
|
|
234
|
+
* }
|
|
235
|
+
* ```
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```tsx
|
|
239
|
+
* // Infinite scroll with sentinel element
|
|
240
|
+
* function InfiniteList() {
|
|
241
|
+
* const { ref, inView } = useIntersectionObserver({
|
|
242
|
+
* threshold: 1.0,
|
|
243
|
+
* rootMargin: '100px',
|
|
244
|
+
* });
|
|
245
|
+
*
|
|
246
|
+
* useEffect(() => {
|
|
247
|
+
* if (inView) {
|
|
248
|
+
* loadMoreItems();
|
|
249
|
+
* }
|
|
250
|
+
* }, [inView]);
|
|
251
|
+
*
|
|
252
|
+
* return (
|
|
253
|
+
* <div>
|
|
254
|
+
* {items.map(item => <Item key={item.id} {...item} />)}
|
|
255
|
+
* <div ref={ref} />
|
|
256
|
+
* </div>
|
|
257
|
+
* );
|
|
258
|
+
* }
|
|
259
|
+
* ```
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* ```tsx
|
|
263
|
+
* // Track scroll progress with multiple thresholds
|
|
264
|
+
* function ProgressTracker() {
|
|
265
|
+
* const { ref, entry } = useIntersectionObserver({
|
|
266
|
+
* threshold: [0, 0.25, 0.5, 0.75, 1.0],
|
|
267
|
+
* onChange: (entry, inView) => {
|
|
268
|
+
* console.log('Progress:', Math.round(entry.intersectionRatio * 100), '%');
|
|
269
|
+
* },
|
|
270
|
+
* });
|
|
271
|
+
*
|
|
272
|
+
* return <div ref={ref}>Long content...</div>;
|
|
273
|
+
* }
|
|
274
|
+
* ```
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```tsx
|
|
278
|
+
* // Custom scroll container as root
|
|
279
|
+
* function ScrollContainer() {
|
|
280
|
+
* const containerRef = useRef<HTMLDivElement>(null);
|
|
281
|
+
* const { ref, inView } = useIntersectionObserver({
|
|
282
|
+
* root: containerRef.current,
|
|
283
|
+
* rootMargin: '0px',
|
|
284
|
+
* });
|
|
285
|
+
*
|
|
286
|
+
* return (
|
|
287
|
+
* <div ref={containerRef} style={{ overflow: 'auto', height: 400 }}>
|
|
288
|
+
* <div style={{ height: 1000 }}>
|
|
289
|
+
* <div ref={ref}>{inView ? 'In container view' : 'Outside'}</div>
|
|
290
|
+
* </div>
|
|
291
|
+
* </div>
|
|
292
|
+
* );
|
|
293
|
+
* }
|
|
294
|
+
* ```
|
|
295
|
+
*/
|
|
296
|
+
declare function useIntersectionObserver(options?: UseIntersectionObserverOptions): UseIntersectionObserverReturn;
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Check if IntersectionObserver API is supported in the current environment
|
|
300
|
+
* Returns false in SSR environments or browsers without support
|
|
301
|
+
*/
|
|
302
|
+
declare function isIntersectionObserverSupported(): boolean;
|
|
303
|
+
/**
|
|
304
|
+
* Convert a native IntersectionObserverEntry to our IntersectionEntry type
|
|
305
|
+
* Provides a consistent interface with additional convenience properties
|
|
306
|
+
*
|
|
307
|
+
* @param nativeEntry - The native IntersectionObserverEntry from the browser
|
|
308
|
+
* @returns IntersectionEntry with all properties
|
|
309
|
+
*/
|
|
310
|
+
declare function toIntersectionEntry(nativeEntry: IntersectionObserverEntry): IntersectionEntry;
|
|
311
|
+
/**
|
|
312
|
+
* Create an initial IntersectionEntry for SSR or before first observation
|
|
313
|
+
* Used when initialIsIntersecting is true
|
|
314
|
+
*
|
|
315
|
+
* @param isIntersecting - Whether to set initial state as intersecting
|
|
316
|
+
* @param target - Optional target element (null for SSR)
|
|
317
|
+
* @returns A mock IntersectionEntry
|
|
318
|
+
*/
|
|
319
|
+
declare function createInitialEntry(isIntersecting: boolean, target?: Element | null): IntersectionEntry | null;
|
|
320
|
+
|
|
321
|
+
export { type IntersectionEntry, type OnChangeCallback, type UseIntersectionObserverOptions, type UseIntersectionObserverReturn, createInitialEntry, isIntersectionObserverSupported, toIntersectionEntry, useIntersectionObserver };
|