@usefy/use-intersection-observer 0.0.1 → 0.0.30
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 +61 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -116,39 +116,39 @@ A hook that observes element visibility using the Intersection Observer API.
|
|
|
116
116
|
|
|
117
117
|
#### Options
|
|
118
118
|
|
|
119
|
-
| Option | Type | Default | Description
|
|
120
|
-
| ----------------------- | ----------------------------------------------------- | ------- |
|
|
121
|
-
| `threshold` | `number \| number[]` | `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
|
|
125
|
-
| `enabled` | `boolean` | `true` | Enable/disable
|
|
126
|
-
| `initialIsIntersecting` | `boolean` | `false` | Initial intersection state (useful for SSR/SSG)
|
|
127
|
-
| `onChange` | `(entry: IntersectionEntry, inView: boolean) => void` | — | Callback when
|
|
128
|
-
| `delay` | `number` | `0` | Delay in milliseconds before creating observer
|
|
119
|
+
| Option | Type | Default | Description |
|
|
120
|
+
| ----------------------- | ----------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------------- |
|
|
121
|
+
| `threshold` | `number \| number[]` | `0` | Visibility ratio(s) that trigger updates (0.0 to 1.0). Updates occur when crossing these boundaries |
|
|
122
|
+
| `root` | `Element \| Document \| null` | `null` | Root element for intersection (null = viewport) |
|
|
123
|
+
| `rootMargin` | `string` | `"0px"` | Margin around root (CSS margin syntax). Positive expands, negative shrinks detection area |
|
|
124
|
+
| `triggerOnce` | `boolean` | `false` | Stop observing after element first becomes visible |
|
|
125
|
+
| `enabled` | `boolean` | `true` | Enable/disable observer. When false, observer disconnects and stops all updates |
|
|
126
|
+
| `initialIsIntersecting` | `boolean` | `false` | Initial intersection state before first observation (useful for SSR/SSG) |
|
|
127
|
+
| `onChange` | `(entry: IntersectionEntry, inView: boolean) => void` | — | Callback fired when isIntersecting or intersectionRatio changes |
|
|
128
|
+
| `delay` | `number` | `0` | Delay in milliseconds before creating the observer (not individual events) |
|
|
129
129
|
|
|
130
130
|
#### Returns `UseIntersectionObserverReturn`
|
|
131
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
|
|
136
|
-
| `ref` | `(node: Element \| null) => void` |
|
|
132
|
+
| Property | Type | Description |
|
|
133
|
+
| -------- | --------------------------------- | ------------------------------------------------------------------------------ |
|
|
134
|
+
| `entry` | `IntersectionEntry \| null` | Intersection entry data (null if not yet observed). Updates trigger re-renders |
|
|
135
|
+
| `inView` | `boolean` | Whether the element is currently intersecting (convenience derived from entry) |
|
|
136
|
+
| `ref` | `(node: Element \| null) => void` | Callback ref to attach to the target element you want to observe |
|
|
137
137
|
|
|
138
138
|
#### `IntersectionEntry`
|
|
139
139
|
|
|
140
140
|
Extended intersection entry with convenience properties:
|
|
141
141
|
|
|
142
|
-
| Property | Type | Description
|
|
143
|
-
| -------------------- | --------------------------- |
|
|
144
|
-
| `entry` | `IntersectionObserverEntry` | Original native
|
|
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` |
|
|
149
|
-
| `intersectionRect` | `DOMRectReadOnly` | Visible portion of target
|
|
150
|
-
| `rootBounds` | `DOMRectReadOnly \| null` |
|
|
151
|
-
| `time` | `number` |
|
|
142
|
+
| Property | Type | Description |
|
|
143
|
+
| -------------------- | --------------------------- | ---------------------------------------------------------------- |
|
|
144
|
+
| `entry` | `IntersectionObserverEntry` | Original native IntersectionObserverEntry from the browser API |
|
|
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 DOM element |
|
|
148
|
+
| `boundingClientRect` | `DOMRectReadOnly` | Target element's bounding box relative to viewport |
|
|
149
|
+
| `intersectionRect` | `DOMRectReadOnly` | Visible portion's bounding box (intersection of target and root) |
|
|
150
|
+
| `rootBounds` | `DOMRectReadOnly \| null` | Root element's bounding box (null if root is the viewport) |
|
|
151
|
+
| `time` | `number` | DOMHighResTimeStamp when intersection was recorded |
|
|
152
152
|
|
|
153
153
|
---
|
|
154
154
|
|
|
@@ -176,9 +176,9 @@ function LazyImage({ src, alt }: { src: string; alt: string }) {
|
|
|
176
176
|
const [loaded, setLoaded] = useState(false);
|
|
177
177
|
|
|
178
178
|
const { ref, inView } = useIntersectionObserver({
|
|
179
|
-
triggerOnce: true,
|
|
180
|
-
threshold: 0.1,
|
|
181
|
-
rootMargin: "50px", //
|
|
179
|
+
triggerOnce: true, // Stop observing after first detection
|
|
180
|
+
threshold: 0.1, // Trigger when 10% visible
|
|
181
|
+
rootMargin: "50px", // Start loading 50px before entering viewport
|
|
182
182
|
});
|
|
183
183
|
|
|
184
184
|
return (
|
|
@@ -209,8 +209,8 @@ function InfiniteList() {
|
|
|
209
209
|
const [loading, setLoading] = useState(false);
|
|
210
210
|
|
|
211
211
|
const { ref, inView } = useIntersectionObserver({
|
|
212
|
-
threshold: 1.0,
|
|
213
|
-
rootMargin: "100px", //
|
|
212
|
+
threshold: 1.0, // Trigger when sentinel is fully visible
|
|
213
|
+
rootMargin: "100px", // Start loading 100px before sentinel enters viewport
|
|
214
214
|
});
|
|
215
215
|
|
|
216
216
|
useEffect(() => {
|
|
@@ -228,7 +228,7 @@ function InfiniteList() {
|
|
|
228
228
|
{items.map((item) => (
|
|
229
229
|
<Item key={item.id} {...item} />
|
|
230
230
|
))}
|
|
231
|
-
{/* Sentinel Element */}
|
|
231
|
+
{/* Sentinel Element - triggers loading when visible */}
|
|
232
232
|
<div ref={ref}>{loading && <Spinner />}</div>
|
|
233
233
|
</div>
|
|
234
234
|
);
|
|
@@ -242,8 +242,8 @@ import { useIntersectionObserver } from "@usefy/use-intersection-observer";
|
|
|
242
242
|
|
|
243
243
|
function AnimatedCard({ children }: { children: React.ReactNode }) {
|
|
244
244
|
const { ref, inView } = useIntersectionObserver({
|
|
245
|
-
triggerOnce: true,
|
|
246
|
-
threshold: 0.3,
|
|
245
|
+
triggerOnce: true, // Animate only once
|
|
246
|
+
threshold: 0.3, // Trigger when 30% visible
|
|
247
247
|
});
|
|
248
248
|
|
|
249
249
|
return (
|
|
@@ -270,12 +270,13 @@ import { useIntersectionObserver } from "@usefy/use-intersection-observer";
|
|
|
270
270
|
function ProgressTracker() {
|
|
271
271
|
const [progress, setProgress] = useState(0);
|
|
272
272
|
|
|
273
|
-
// 101 thresholds (0%, 1%, 2%, ... 100%)
|
|
273
|
+
// 101 thresholds (0%, 1%, 2%, ... 100%) for fine-grained tracking
|
|
274
274
|
const thresholds = Array.from({ length: 101 }, (_, i) => i / 100);
|
|
275
275
|
|
|
276
276
|
const { ref } = useIntersectionObserver({
|
|
277
277
|
threshold: thresholds,
|
|
278
278
|
onChange: (entry) => {
|
|
279
|
+
// Update progress when crossing any threshold boundary
|
|
279
280
|
setProgress(Math.round(entry.intersectionRatio * 100));
|
|
280
281
|
},
|
|
281
282
|
});
|
|
@@ -350,6 +351,7 @@ function Section({ id, onVisible }: { id: string; onVisible: () => void }) {
|
|
|
350
351
|
const { ref } = useIntersectionObserver({
|
|
351
352
|
threshold: 0.6, // Activate when 60% visible
|
|
352
353
|
onChange: (_, inView) => {
|
|
354
|
+
// Called when section enters or exits the 60% visibility threshold
|
|
353
355
|
if (inView) onVisible();
|
|
354
356
|
},
|
|
355
357
|
});
|
|
@@ -372,7 +374,7 @@ function ConditionalObserver() {
|
|
|
372
374
|
const [isLoading, setIsLoading] = useState(true);
|
|
373
375
|
|
|
374
376
|
const { ref, inView } = useIntersectionObserver({
|
|
375
|
-
enabled: !isLoading, //
|
|
377
|
+
enabled: !isLoading, // Observer is disconnected when disabled
|
|
376
378
|
});
|
|
377
379
|
|
|
378
380
|
return <div ref={ref}>{inView ? "Observing" : "Not observing"}</div>;
|
|
@@ -385,12 +387,13 @@ function ConditionalObserver() {
|
|
|
385
387
|
import { useIntersectionObserver } from "@usefy/use-intersection-observer";
|
|
386
388
|
|
|
387
389
|
function SSRComponent() {
|
|
388
|
-
//
|
|
390
|
+
// Set initial state for server-side rendering
|
|
389
391
|
const { ref, inView } = useIntersectionObserver({
|
|
390
|
-
initialIsIntersecting: true,
|
|
392
|
+
initialIsIntersecting: true, // Assume visible during SSR
|
|
391
393
|
});
|
|
392
394
|
|
|
393
|
-
//
|
|
395
|
+
// During SSR/first render, inView will be true
|
|
396
|
+
// After hydration, actual intersection state takes over
|
|
394
397
|
return <div ref={ref}>{inView ? "Initially visible" : "Not visible"}</div>;
|
|
395
398
|
}
|
|
396
399
|
```
|
|
@@ -401,12 +404,13 @@ function SSRComponent() {
|
|
|
401
404
|
import { useIntersectionObserver } from "@usefy/use-intersection-observer";
|
|
402
405
|
|
|
403
406
|
function DelayedObserver() {
|
|
404
|
-
// Delay observer creation by 500ms
|
|
405
|
-
// Useful for preventing premature observations during fast scrolling
|
|
406
407
|
const { ref, inView } = useIntersectionObserver({
|
|
407
|
-
delay: 500,
|
|
408
|
+
delay: 500, // Wait 500ms before creating the observer
|
|
408
409
|
});
|
|
409
410
|
|
|
411
|
+
// Observer is NOT created until 500ms after component mount
|
|
412
|
+
// This delays the CREATION of the observer, not individual intersection events
|
|
413
|
+
// Useful for preventing premature observations during page load or fast scrolling
|
|
410
414
|
return <div ref={ref}>{inView ? "Observing" : "Not observing"}</div>;
|
|
411
415
|
}
|
|
412
416
|
```
|
|
@@ -415,13 +419,24 @@ function DelayedObserver() {
|
|
|
415
419
|
|
|
416
420
|
## Performance Optimization
|
|
417
421
|
|
|
418
|
-
The
|
|
422
|
+
The Intersection Observer API fires callbacks when threshold boundaries are crossed or when `isIntersecting` changes (e.g., during user scroll interactions). When a callback fires:
|
|
419
423
|
|
|
420
|
-
-
|
|
421
|
-
-
|
|
422
|
-
- ❌ Does NOT re-render when only `time` changes (time updates on every intersection callback, but doesn't trigger re-renders alone)
|
|
424
|
+
- The `entry` object is updated with new values including `time` (timestamp of the intersection event)
|
|
425
|
+
- `setEntry()` is called → **re-render occurs**
|
|
423
426
|
|
|
424
|
-
|
|
427
|
+
The hook includes a safeguard: it compares the previous `isIntersecting` and `intersectionRatio` values with the new ones before calling `setEntry()`. This prevents redundant re-renders in edge cases where the observer might report the same state multiple times.
|
|
428
|
+
|
|
429
|
+
```tsx
|
|
430
|
+
// Inside the hook's callback:
|
|
431
|
+
const hasChanged =
|
|
432
|
+
!prevEntry ||
|
|
433
|
+
prevEntry.isIntersecting !== nativeEntry.isIntersecting ||
|
|
434
|
+
prevEntry.intersectionRatio !== nativeEntry.intersectionRatio;
|
|
435
|
+
|
|
436
|
+
if (hasChanged) {
|
|
437
|
+
setEntry(intersectionEntry); // Re-render triggered
|
|
438
|
+
}
|
|
439
|
+
```
|
|
425
440
|
|
|
426
441
|
---
|
|
427
442
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usefy/use-intersection-observer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.30",
|
|
4
4
|
"description": "A React hook for observing element visibility using Intersection Observer API with enterprise-grade features",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|