@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/README.md
ADDED
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/mirunamu00/usefy/master/assets/logo.png" alt="usefy logo" width="120" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">@usefy/use-intersection-observer</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>A powerful React hook for observing element visibility using the Intersection Observer API</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/@usefy/use-intersection-observer">
|
|
13
|
+
<img src="https://img.shields.io/npm/v/@usefy/use-intersection-observer.svg?style=flat-square&color=007acc" alt="npm version" />
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://www.npmjs.com/package/@usefy/use-intersection-observer">
|
|
16
|
+
<img src="https://img.shields.io/npm/dm/@usefy/use-intersection-observer.svg?style=flat-square&color=007acc" alt="npm downloads" />
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://bundlephobia.com/package/@usefy/use-intersection-observer">
|
|
19
|
+
<img src="https://img.shields.io/bundlephobia/minzip/@usefy/use-intersection-observer?style=flat-square&color=007acc" alt="bundle size" />
|
|
20
|
+
</a>
|
|
21
|
+
<a href="https://github.com/mirunamu00/usefy/blob/master/LICENSE">
|
|
22
|
+
<img src="https://img.shields.io/npm/l/@usefy/use-intersection-observer.svg?style=flat-square&color=007acc" alt="license" />
|
|
23
|
+
</a>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<a href="#installation">Installation</a> •
|
|
28
|
+
<a href="#quick-start">Quick Start</a> •
|
|
29
|
+
<a href="#api-reference">API Reference</a> •
|
|
30
|
+
<a href="#examples">Examples</a> •
|
|
31
|
+
<a href="#license">License</a>
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
<p align="center">
|
|
35
|
+
<a href="https://mirunamu00.github.io/usefy/?path=/docs/hooks-useintersectionobserver--docs" target="_blank" rel="noopener noreferrer">
|
|
36
|
+
<strong>📚 View Storybook Demo</strong>
|
|
37
|
+
</a>
|
|
38
|
+
</p>
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Overview
|
|
43
|
+
|
|
44
|
+
`@usefy/use-intersection-observer` is a feature-rich React hook for efficiently detecting element visibility in the viewport using the Intersection Observer API. It provides a simple API for lazy loading, infinite scroll, scroll animations, and more.
|
|
45
|
+
|
|
46
|
+
**Part of the [@usefy](https://www.npmjs.com/org/usefy) ecosystem** — a collection of production-ready React hooks designed for modern applications.
|
|
47
|
+
|
|
48
|
+
### Why use-intersection-observer?
|
|
49
|
+
|
|
50
|
+
- **Zero Dependencies** — Pure React implementation with no external dependencies
|
|
51
|
+
- **TypeScript First** — Full type safety with comprehensive type definitions
|
|
52
|
+
- **Efficient Detection** — Leverages native Intersection Observer API for optimal performance
|
|
53
|
+
- **Threshold-based Callbacks** — Fine-grained visibility ratio tracking with multiple thresholds
|
|
54
|
+
- **TriggerOnce Support** — Perfect for lazy loading patterns
|
|
55
|
+
- **Dynamic Enable/Disable** — Conditional observation support
|
|
56
|
+
- **Custom Root Containers** — Observe elements within custom scroll containers
|
|
57
|
+
- **Root Margin Support** — Expand or shrink detection boundaries
|
|
58
|
+
- **SSR Compatible** — Works seamlessly with Next.js, Remix, and other SSR frameworks
|
|
59
|
+
- **Optimized Re-renders** — Only updates when meaningful values change
|
|
60
|
+
- **Well Tested** — Comprehensive test coverage with Vitest
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# npm
|
|
68
|
+
npm install @usefy/use-intersection-observer
|
|
69
|
+
|
|
70
|
+
# yarn
|
|
71
|
+
yarn add @usefy/use-intersection-observer
|
|
72
|
+
|
|
73
|
+
# pnpm
|
|
74
|
+
pnpm add @usefy/use-intersection-observer
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Peer Dependencies
|
|
78
|
+
|
|
79
|
+
This package requires React 18 or 19:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"peerDependencies": {
|
|
84
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Quick Start
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
import { useIntersectionObserver } from "@usefy/use-intersection-observer";
|
|
95
|
+
|
|
96
|
+
function MyComponent() {
|
|
97
|
+
const { ref, inView, entry } = useIntersectionObserver();
|
|
98
|
+
|
|
99
|
+
return <div ref={ref}>{inView ? "👁️ Visible!" : "👻 Not visible"}</div>;
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## API Reference
|
|
106
|
+
|
|
107
|
+
### `useIntersectionObserver(options?)`
|
|
108
|
+
|
|
109
|
+
A hook that observes element visibility using the Intersection Observer API.
|
|
110
|
+
|
|
111
|
+
#### Parameters
|
|
112
|
+
|
|
113
|
+
| Parameter | Type | Description |
|
|
114
|
+
| --------- | -------------------------------- | ----------------------------- |
|
|
115
|
+
| `options` | `UseIntersectionObserverOptions` | Optional configuration object |
|
|
116
|
+
|
|
117
|
+
#### Options
|
|
118
|
+
|
|
119
|
+
| Option | Type | Default | Description |
|
|
120
|
+
| ----------------------- | ----------------------------------------------------- | ------- | -------------------------------------------------------- |
|
|
121
|
+
| `threshold` | `number \| number[]` | `0` | Threshold(s) at which callback is triggered (0.0 to 1.0) |
|
|
122
|
+
| `root` | `Element \| Document \| null` | `null` | Root element for intersection (null = viewport) |
|
|
123
|
+
| `rootMargin` | `string` | `"0px"` | Margin around root (CSS margin syntax) |
|
|
124
|
+
| `triggerOnce` | `boolean` | `false` | Stop observing after first intersection |
|
|
125
|
+
| `enabled` | `boolean` | `true` | Enable/disable the observer dynamically |
|
|
126
|
+
| `initialIsIntersecting` | `boolean` | `false` | Initial intersection state (useful for SSR/SSG) |
|
|
127
|
+
| `onChange` | `(entry: IntersectionEntry, inView: boolean) => void` | — | Callback when intersection state changes |
|
|
128
|
+
| `delay` | `number` | `0` | Delay in milliseconds before creating observer |
|
|
129
|
+
|
|
130
|
+
#### Returns `UseIntersectionObserverReturn`
|
|
131
|
+
|
|
132
|
+
| Property | Type | Description |
|
|
133
|
+
| -------- | --------------------------------- | -------------------------------------------------- |
|
|
134
|
+
| `entry` | `IntersectionEntry \| null` | Intersection entry data (null if not yet observed) |
|
|
135
|
+
| `inView` | `boolean` | Whether the element is currently in view |
|
|
136
|
+
| `ref` | `(node: Element \| null) => void` | Ref callback to attach to target element |
|
|
137
|
+
|
|
138
|
+
#### `IntersectionEntry`
|
|
139
|
+
|
|
140
|
+
Extended intersection entry with convenience properties:
|
|
141
|
+
|
|
142
|
+
| Property | Type | Description |
|
|
143
|
+
| -------------------- | --------------------------- | ---------------------------------------- |
|
|
144
|
+
| `entry` | `IntersectionObserverEntry` | Original native entry |
|
|
145
|
+
| `isIntersecting` | `boolean` | Whether target is intersecting with root |
|
|
146
|
+
| `intersectionRatio` | `number` | Ratio of target visible (0.0 to 1.0) |
|
|
147
|
+
| `target` | `Element` | The observed element |
|
|
148
|
+
| `boundingClientRect` | `DOMRectReadOnly` | Bounding rectangle of target element |
|
|
149
|
+
| `intersectionRect` | `DOMRectReadOnly` | Visible portion of target element |
|
|
150
|
+
| `rootBounds` | `DOMRectReadOnly \| null` | Bounding rectangle of root element |
|
|
151
|
+
| `time` | `number` | Timestamp when intersection was recorded |
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Examples
|
|
156
|
+
|
|
157
|
+
### Basic Usage
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
import { useIntersectionObserver } from "@usefy/use-intersection-observer";
|
|
161
|
+
|
|
162
|
+
function VisibilityChecker() {
|
|
163
|
+
const { ref, inView } = useIntersectionObserver();
|
|
164
|
+
|
|
165
|
+
return <div ref={ref}>{inView ? "👁️ Visible!" : "👻 Not visible"}</div>;
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Lazy Loading Images
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
import { useState } from "react";
|
|
173
|
+
import { useIntersectionObserver } from "@usefy/use-intersection-observer";
|
|
174
|
+
|
|
175
|
+
function LazyImage({ src, alt }: { src: string; alt: string }) {
|
|
176
|
+
const [loaded, setLoaded] = useState(false);
|
|
177
|
+
|
|
178
|
+
const { ref, inView } = useIntersectionObserver({
|
|
179
|
+
triggerOnce: true,
|
|
180
|
+
threshold: 0.1,
|
|
181
|
+
rootMargin: "50px", // Preload 50px ahead
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<div ref={ref}>
|
|
186
|
+
{inView ? (
|
|
187
|
+
<img
|
|
188
|
+
src={src}
|
|
189
|
+
alt={alt}
|
|
190
|
+
onLoad={() => setLoaded(true)}
|
|
191
|
+
style={{ opacity: loaded ? 1 : 0 }}
|
|
192
|
+
/>
|
|
193
|
+
) : (
|
|
194
|
+
<div className="placeholder">Loading...</div>
|
|
195
|
+
)}
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Infinite Scroll
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
import { useState, useEffect } from "react";
|
|
205
|
+
import { useIntersectionObserver } from "@usefy/use-intersection-observer";
|
|
206
|
+
|
|
207
|
+
function InfiniteList() {
|
|
208
|
+
const [items, setItems] = useState([...initialItems]);
|
|
209
|
+
const [loading, setLoading] = useState(false);
|
|
210
|
+
|
|
211
|
+
const { ref, inView } = useIntersectionObserver({
|
|
212
|
+
threshold: 1.0,
|
|
213
|
+
rootMargin: "100px", // Preload 100px ahead
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
useEffect(() => {
|
|
217
|
+
if (inView && !loading) {
|
|
218
|
+
setLoading(true);
|
|
219
|
+
fetchMoreItems().then((newItems) => {
|
|
220
|
+
setItems((prev) => [...prev, ...newItems]);
|
|
221
|
+
setLoading(false);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}, [inView, loading]);
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<div>
|
|
228
|
+
{items.map((item) => (
|
|
229
|
+
<Item key={item.id} {...item} />
|
|
230
|
+
))}
|
|
231
|
+
{/* Sentinel Element */}
|
|
232
|
+
<div ref={ref}>{loading && <Spinner />}</div>
|
|
233
|
+
</div>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Scroll Animations
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
import { useIntersectionObserver } from "@usefy/use-intersection-observer";
|
|
242
|
+
|
|
243
|
+
function AnimatedCard({ children }: { children: React.ReactNode }) {
|
|
244
|
+
const { ref, inView } = useIntersectionObserver({
|
|
245
|
+
triggerOnce: true,
|
|
246
|
+
threshold: 0.3,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<div
|
|
251
|
+
ref={ref}
|
|
252
|
+
style={{
|
|
253
|
+
opacity: inView ? 1 : 0,
|
|
254
|
+
transform: inView ? "translateY(0)" : "translateY(30px)",
|
|
255
|
+
transition: "all 0.6s ease",
|
|
256
|
+
}}
|
|
257
|
+
>
|
|
258
|
+
{children}
|
|
259
|
+
</div>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Reading Progress Tracker
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
import { useState } from "react";
|
|
268
|
+
import { useIntersectionObserver } from "@usefy/use-intersection-observer";
|
|
269
|
+
|
|
270
|
+
function ProgressTracker() {
|
|
271
|
+
const [progress, setProgress] = useState(0);
|
|
272
|
+
|
|
273
|
+
// 101 thresholds (0%, 1%, 2%, ... 100%)
|
|
274
|
+
const thresholds = Array.from({ length: 101 }, (_, i) => i / 100);
|
|
275
|
+
|
|
276
|
+
const { ref } = useIntersectionObserver({
|
|
277
|
+
threshold: thresholds,
|
|
278
|
+
onChange: (entry) => {
|
|
279
|
+
setProgress(Math.round(entry.intersectionRatio * 100));
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
return (
|
|
284
|
+
<>
|
|
285
|
+
<div className="progress-bar" style={{ width: `${progress}%` }} />
|
|
286
|
+
<article ref={ref}>{/* Long content */}</article>
|
|
287
|
+
</>
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Custom Scroll Container
|
|
293
|
+
|
|
294
|
+
```tsx
|
|
295
|
+
import { useRef } from "react";
|
|
296
|
+
import { useIntersectionObserver } from "@usefy/use-intersection-observer";
|
|
297
|
+
|
|
298
|
+
function ScrollContainer() {
|
|
299
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
300
|
+
|
|
301
|
+
const { ref, inView } = useIntersectionObserver({
|
|
302
|
+
root: containerRef.current,
|
|
303
|
+
rootMargin: "0px",
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
return (
|
|
307
|
+
<div ref={containerRef} style={{ overflow: "auto", height: 400 }}>
|
|
308
|
+
<div style={{ height: 1000 }}>
|
|
309
|
+
<div ref={ref}>{inView ? "Visible in container" : "Not visible"}</div>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Section Navigation Highlighting
|
|
317
|
+
|
|
318
|
+
```tsx
|
|
319
|
+
import { useState } from "react";
|
|
320
|
+
import { useIntersectionObserver } from "@usefy/use-intersection-observer";
|
|
321
|
+
|
|
322
|
+
function SectionNavigation() {
|
|
323
|
+
const [activeSection, setActiveSection] = useState<string | null>(null);
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<>
|
|
327
|
+
<nav>
|
|
328
|
+
{sections.map((section) => (
|
|
329
|
+
<button
|
|
330
|
+
key={section.id}
|
|
331
|
+
className={activeSection === section.id ? "active" : ""}
|
|
332
|
+
>
|
|
333
|
+
{section.name}
|
|
334
|
+
</button>
|
|
335
|
+
))}
|
|
336
|
+
</nav>
|
|
337
|
+
|
|
338
|
+
{sections.map((section) => (
|
|
339
|
+
<Section
|
|
340
|
+
key={section.id}
|
|
341
|
+
id={section.id}
|
|
342
|
+
onVisible={() => setActiveSection(section.id)}
|
|
343
|
+
/>
|
|
344
|
+
))}
|
|
345
|
+
</>
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function Section({ id, onVisible }: { id: string; onVisible: () => void }) {
|
|
350
|
+
const { ref } = useIntersectionObserver({
|
|
351
|
+
threshold: 0.6, // Activate when 60% visible
|
|
352
|
+
onChange: (_, inView) => {
|
|
353
|
+
if (inView) onVisible();
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
return (
|
|
358
|
+
<section ref={ref} id={id}>
|
|
359
|
+
...
|
|
360
|
+
</section>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Dynamic Enable/Disable
|
|
366
|
+
|
|
367
|
+
```tsx
|
|
368
|
+
import { useState } from "react";
|
|
369
|
+
import { useIntersectionObserver } from "@usefy/use-intersection-observer";
|
|
370
|
+
|
|
371
|
+
function ConditionalObserver() {
|
|
372
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
373
|
+
|
|
374
|
+
const { ref, inView } = useIntersectionObserver({
|
|
375
|
+
enabled: !isLoading, // Disable while loading
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
return <div ref={ref}>{inView ? "Observing" : "Not observing"}</div>;
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### SSR/SSG Support
|
|
383
|
+
|
|
384
|
+
```tsx
|
|
385
|
+
import { useIntersectionObserver } from "@usefy/use-intersection-observer";
|
|
386
|
+
|
|
387
|
+
function SSRComponent() {
|
|
388
|
+
// Assume above-the-fold content is initially visible
|
|
389
|
+
const { ref, inView } = useIntersectionObserver({
|
|
390
|
+
initialIsIntersecting: true,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// On SSR, inView will be true on first render
|
|
394
|
+
return <div ref={ref}>{inView ? "Initially visible" : "Not visible"}</div>;
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Delay Observer Creation
|
|
399
|
+
|
|
400
|
+
```tsx
|
|
401
|
+
import { useIntersectionObserver } from "@usefy/use-intersection-observer";
|
|
402
|
+
|
|
403
|
+
function DelayedObserver() {
|
|
404
|
+
// Delay observer creation by 500ms
|
|
405
|
+
// Useful for preventing premature observations during fast scrolling
|
|
406
|
+
const { ref, inView } = useIntersectionObserver({
|
|
407
|
+
delay: 500,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
return <div ref={ref}>{inView ? "Observing" : "Not observing"}</div>;
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Performance Optimization
|
|
417
|
+
|
|
418
|
+
The hook is optimized to **only trigger re-renders when meaningful visibility values change**, not on every intersection callback. This means:
|
|
419
|
+
|
|
420
|
+
- ✅ Re-renders when `isIntersecting` changes (element enters/exits view)
|
|
421
|
+
- ✅ Re-renders when `intersectionRatio` changes (visibility percentage changes)
|
|
422
|
+
- ❌ Does NOT re-render when only `time` changes (time updates on every intersection callback, but doesn't trigger re-renders alone)
|
|
423
|
+
|
|
424
|
+
When an intersection occurs, the `time` property is updated with a new timestamp, but the hook compares `isIntersecting` and `intersectionRatio` to determine if a re-render is needed. This prevents unnecessary re-renders during scrolling while maintaining accurate visibility detection.
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## TypeScript
|
|
429
|
+
|
|
430
|
+
This hook is written in TypeScript and exports comprehensive type definitions.
|
|
431
|
+
|
|
432
|
+
```tsx
|
|
433
|
+
import {
|
|
434
|
+
useIntersectionObserver,
|
|
435
|
+
type UseIntersectionObserverOptions,
|
|
436
|
+
type UseIntersectionObserverReturn,
|
|
437
|
+
type IntersectionEntry,
|
|
438
|
+
type OnChangeCallback,
|
|
439
|
+
} from "@usefy/use-intersection-observer";
|
|
440
|
+
|
|
441
|
+
// Full type inference
|
|
442
|
+
const { ref, inView, entry }: UseIntersectionObserverReturn =
|
|
443
|
+
useIntersectionObserver({
|
|
444
|
+
threshold: 0.5,
|
|
445
|
+
onChange: (entry, inView) => {
|
|
446
|
+
console.log("Visibility changed:", inView);
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## Performance
|
|
454
|
+
|
|
455
|
+
- **Stable Function References** — The `ref` callback is memoized with `useCallback`
|
|
456
|
+
- **Smart Re-renders** — Only re-renders when `isIntersecting` or `intersectionRatio` changes
|
|
457
|
+
- **Native API** — Leverages browser's Intersection Observer API for optimal performance
|
|
458
|
+
- **SSR Compatible** — Gracefully degrades in server environments
|
|
459
|
+
|
|
460
|
+
```tsx
|
|
461
|
+
const { ref } = useIntersectionObserver({
|
|
462
|
+
threshold: [0, 0.5, 1.0],
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// ref reference remains stable across renders
|
|
466
|
+
useEffect(() => {
|
|
467
|
+
// Safe to use as dependency
|
|
468
|
+
}, [ref]);
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## Browser Support
|
|
474
|
+
|
|
475
|
+
This hook uses the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API), which is supported in:
|
|
476
|
+
|
|
477
|
+
- Chrome 51+
|
|
478
|
+
- Firefox 55+
|
|
479
|
+
- Safari 12.1+
|
|
480
|
+
- Edge 15+
|
|
481
|
+
- Opera 38+
|
|
482
|
+
|
|
483
|
+
For unsupported browsers, the hook gracefully degrades and returns the initial state.
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
## Testing
|
|
488
|
+
|
|
489
|
+
This package maintains comprehensive test coverage to ensure reliability and stability.
|
|
490
|
+
|
|
491
|
+
### Test Coverage
|
|
492
|
+
|
|
493
|
+
📊 <a href="https://mirunamu00.github.io/usefy/coverage/use-intersection-observer/src/index.html" target="_blank" rel="noopener noreferrer"><strong>View Detailed Coverage Report</strong></a> (GitHub Pages)
|
|
494
|
+
|
|
495
|
+
### Test Files
|
|
496
|
+
|
|
497
|
+
- `useIntersectionObserver.test.ts` — 87 tests for hook behavior
|
|
498
|
+
- `utils.test.ts` — 63 tests for utility functions
|
|
499
|
+
|
|
500
|
+
**Total: 150 tests**
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## License
|
|
505
|
+
|
|
506
|
+
MIT © [mirunamu](https://github.com/mirunamu00)
|
|
507
|
+
|
|
508
|
+
This package is part of the [usefy](https://github.com/mirunamu00/usefy) monorepo.
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
<p align="center">
|
|
513
|
+
<sub>Built with care by the usefy team</sub>
|
|
514
|
+
</p>
|