@usefy/use-intersection-observer 0.0.1 → 0.0.29

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.
Files changed (2) hide show
  1. package/README.md +61 -46
  2. 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` | 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 |
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 in view |
136
- | `ref` | `(node: Element \| null) => void` | Ref callback to attach to target element |
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 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 |
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", // Preload 50px ahead
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", // Preload 100px ahead
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, // Disable while loading
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
- // Assume above-the-fold content is initially visible
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
- // On SSR, inView will be true on first render
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 hook is optimized to **only trigger re-renders when meaningful visibility values change**, not on every intersection callback. This means:
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
- - 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)
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
- 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.
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.1",
3
+ "version": "0.0.29",
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",