react-intersection-observer-hook
Advanced tools
Comparing version
@@ -5,5 +5,5 @@ type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; | ||
type IntersectionObserverHookRefCallbackNode = Element | null; | ||
type IntersectionObserverHookRefCallback = (node: IntersectionObserverHookRefCallbackNode) => void; | ||
type IntersectionObserverHookRefCallback = (node: IntersectionObserverHookRefCallbackNode) => VoidFunction; | ||
type IntersectionObserverHookRootRefCallbackNode = IntersectionObserverInit['root']; | ||
type IntersectionObserverHookRootRefCallback = (node: IntersectionObserverHookRootRefCallbackNode) => void; | ||
type IntersectionObserverHookRootRefCallback = (node: IntersectionObserverHookRootRefCallbackNode) => VoidFunction; | ||
type IntersectionObserverHookResult = [ | ||
@@ -18,3 +18,5 @@ IntersectionObserverHookRefCallback, | ||
type TrackVisibilityHookArgs = IntersectionObserverHookArgs; | ||
type TrackVisibilityHookArgs = IntersectionObserverHookArgs & { | ||
once?: boolean; | ||
}; | ||
type TrackVisibilityHookResult = [ | ||
@@ -24,7 +26,6 @@ IntersectionObserverHookResult[0], | ||
isVisible: boolean; | ||
wasEverVisible: boolean; | ||
} | ||
]; | ||
declare function useTrackVisibility(args?: IntersectionObserverHookArgs): TrackVisibilityHookResult; | ||
declare function useTrackVisibility(args?: TrackVisibilityHookArgs): TrackVisibilityHookResult; | ||
export { type IntersectionObserverHookArgs, type IntersectionObserverHookRefCallback, type IntersectionObserverHookRefCallbackNode, type IntersectionObserverHookResult, type IntersectionObserverHookRootRefCallback, type IntersectionObserverHookRootRefCallbackNode, type TrackVisibilityHookArgs, type TrackVisibilityHookResult, useIntersectionObserver, useTrackVisibility }; |
@@ -1,1 +0,154 @@ | ||
"use strict";var I=Object.defineProperty;var d=Object.getOwnPropertyDescriptor;var H=Object.getOwnPropertyNames;var E=Object.prototype.hasOwnProperty;var m=(r,e)=>{for(var t in e)I(r,t,{get:e[t],enumerable:!0})},h=(r,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of H(e))!E.call(r,o)&&o!==t&&I(r,o,{get:()=>e[o],enumerable:!(s=d(e,o))||s.enumerable});return r};var g=r=>h(I({},"__esModule",{value:!0}),r);var M={};m(M,{useIntersectionObserver:()=>y,useTrackVisibility:()=>R});module.exports=g(M);var i=require("react");function p(){let r=new Map;function e({root:t,rootMargin:s,threshold:o}){let c=r.get(t);c||(c=new Map,r.set(t,c));let k=JSON.stringify({rootMargin:s,threshold:o}),b=c.get(k);if(!b){let n=new Map;b={observer:new IntersectionObserver(u=>{u.forEach(O=>{n.get(O.target)?.(O)})},{root:t,rootMargin:s,threshold:o}),entryCallbacks:n},c.set(k,b)}return{observe:(n,a)=>{b.entryCallbacks.set(n,a),b.observer.observe(n)},unobserve:n=>{b.entryCallbacks.delete(n),b.observer.unobserve(n)}}}return{getObserver:e}}var x="0px",V=[0],A=p();function T(r){let e=r?.rootMargin??x,t=r?.threshold??V,s=(0,i.useRef)(null),o=(0,i.useRef)(null),c=(0,i.useRef)(null),[k,b]=(0,i.useState)(),n=(0,i.useCallback)(()=>{let l=s.current;if(!l){b(void 0);return}let v=A.getObserver({root:o.current,rootMargin:e,threshold:t});v.observe(l,C=>{b(C)}),c.current=v},[e,t]),a=(0,i.useCallback)(()=>{let l=c.current,v=s.current;v&&l?.unobserve(v),c.current=null},[]),u=(0,i.useCallback)(l=>{a(),s.current=l,n()},[n,a]),O=(0,i.useCallback)(l=>{a(),o.current=l,n()},[n,a]);return[u,{entry:k,rootRef:O}]}var y=T;var f=require("react");function N(r){let[e,t]=y(r),s=!!t.entry?.isIntersecting,[o,c]=(0,f.useState)(s);return s&&!o&&c(!0),[e,{...t,isVisible:s,wasEverVisible:o}]}var R=N;0&&(module.exports={useIntersectionObserver,useTrackVisibility}); | ||
"use strict"; | ||
var __defProp = Object.defineProperty; | ||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
var __getOwnPropNames = Object.getOwnPropertyNames; | ||
var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
var __export = (target, all) => { | ||
for (var name in all) | ||
__defProp(target, name, { get: all[name], enumerable: true }); | ||
}; | ||
var __copyProps = (to, from, except, desc) => { | ||
if (from && typeof from === "object" || typeof from === "function") { | ||
for (let key of __getOwnPropNames(from)) | ||
if (!__hasOwnProp.call(to, key) && key !== except) | ||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
} | ||
return to; | ||
}; | ||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
// src/index.ts | ||
var src_exports = {}; | ||
__export(src_exports, { | ||
useIntersectionObserver: () => use_intersection_observer_default, | ||
useTrackVisibility: () => use_track_visibility_default | ||
}); | ||
module.exports = __toCommonJS(src_exports); | ||
// src/use-intersection-observer.ts | ||
var import_react = require("react"); | ||
// src/utils.ts | ||
function createObserverCache() { | ||
const cachesByRoot = /* @__PURE__ */ new Map(); | ||
function getObserver({ | ||
root, | ||
rootMargin, | ||
threshold | ||
}) { | ||
let cacheByRoot = cachesByRoot.get(root); | ||
if (!cacheByRoot) { | ||
cacheByRoot = /* @__PURE__ */ new Map(); | ||
cachesByRoot.set(root, cacheByRoot); | ||
} | ||
const cacheKey = JSON.stringify({ rootMargin, threshold }); | ||
let cachedObserver = cacheByRoot.get(cacheKey); | ||
if (!cachedObserver) { | ||
const entryCallbacks = /* @__PURE__ */ new Map(); | ||
const observer = new IntersectionObserver( | ||
(entries) => { | ||
entries.forEach((entry) => { | ||
const callback = entryCallbacks.get(entry.target); | ||
callback?.(entry); | ||
}); | ||
}, | ||
{ root, rootMargin, threshold } | ||
); | ||
cachedObserver = { observer, entryCallbacks }; | ||
cacheByRoot.set(cacheKey, cachedObserver); | ||
} | ||
return { | ||
observe: (node, callback) => { | ||
cachedObserver.entryCallbacks.set(node, callback); | ||
cachedObserver.observer.observe(node); | ||
}, | ||
unobserve: (node) => { | ||
cachedObserver.entryCallbacks.delete(node); | ||
cachedObserver.observer.unobserve(node); | ||
} | ||
}; | ||
} | ||
return { getObserver }; | ||
} | ||
// src/use-intersection-observer.ts | ||
var DEFAULT_ROOT_MARGIN = "0px"; | ||
var DEFAULT_THRESHOLD = [0]; | ||
var observerCache = createObserverCache(); | ||
function useIntersectionObserver(args) { | ||
const rootMargin = args?.rootMargin ?? DEFAULT_ROOT_MARGIN; | ||
const threshold = args?.threshold ?? DEFAULT_THRESHOLD; | ||
const nodeRef = (0, import_react.useRef)(null); | ||
const rootRef = (0, import_react.useRef)(null); | ||
const observerRef = (0, import_react.useRef)(null); | ||
const [entry, setEntry] = (0, import_react.useState)(); | ||
const reinitializeObserver = (0, import_react.useCallback)(() => { | ||
function cleanupObserver() { | ||
const observer = observerRef.current; | ||
const node = nodeRef.current; | ||
if (node) { | ||
observer?.unobserve(node); | ||
setEntry(void 0); | ||
} | ||
observerRef.current = null; | ||
} | ||
function initializeObserver() { | ||
const node = nodeRef.current; | ||
if (!node) return; | ||
const observer = observerCache.getObserver({ | ||
root: rootRef.current, | ||
rootMargin, | ||
threshold | ||
}); | ||
observer.observe(node, (observedEntry) => { | ||
setEntry(observedEntry); | ||
}); | ||
observerRef.current = observer; | ||
} | ||
cleanupObserver(); | ||
initializeObserver(); | ||
}, [rootMargin, threshold]); | ||
const refCallback = (0, import_react.useCallback)( | ||
(node) => { | ||
nodeRef.current = node; | ||
reinitializeObserver(); | ||
return () => { | ||
nodeRef.current = null; | ||
reinitializeObserver(); | ||
}; | ||
}, | ||
[reinitializeObserver] | ||
); | ||
const rootRefCallback = (0, import_react.useCallback)( | ||
(rootNode) => { | ||
rootRef.current = rootNode; | ||
reinitializeObserver(); | ||
return () => { | ||
rootRef.current = null; | ||
reinitializeObserver(); | ||
}; | ||
}, | ||
[reinitializeObserver] | ||
); | ||
return [refCallback, { entry, rootRef: rootRefCallback }]; | ||
} | ||
var use_intersection_observer_default = useIntersectionObserver; | ||
// src/use-track-visibility.ts | ||
var import_react2 = require("react"); | ||
function useTrackVisibility(args) { | ||
const { once, ...rest } = args ?? {}; | ||
const [ref, result] = use_intersection_observer_default(rest); | ||
const isVisible = Boolean(result.entry?.isIntersecting); | ||
const [isVisibleOnce, setIsVisibleOnce] = (0, import_react2.useState)(isVisible); | ||
if (once && isVisible && !isVisibleOnce) { | ||
setIsVisibleOnce(true); | ||
} | ||
return [ref, { ...result, isVisible: once ? isVisibleOnce : isVisible }]; | ||
} | ||
var use_track_visibility_default = useTrackVisibility; | ||
// Annotate the CommonJS export names for ESM import in node: | ||
0 && (module.exports = { | ||
useIntersectionObserver, | ||
useTrackVisibility | ||
}); |
{ | ||
"name": "react-intersection-observer-hook", | ||
"version": "3.0.1", | ||
"version": "4.0.0", | ||
"description": "React hook to use IntersectionObserver declaratively.", | ||
@@ -42,27 +42,27 @@ "keywords": [ | ||
"scripts": { | ||
"attw:check": "attw --pack .", | ||
"attw": "attw --pack .", | ||
"build": "tsup", | ||
"dev": "tsup --watch", | ||
"lint:check": "eslint . --max-warnings 0", | ||
"lint": "eslint . --max-warnings 0", | ||
"lint:fix": "eslint . --fix --max-warnings 0", | ||
"prepublishOnly": "npm run build", | ||
"publint:check": "publint", | ||
"types:check": "tsc" | ||
"publint": "publint", | ||
"typecheck": "tsc" | ||
}, | ||
"devDependencies": { | ||
"@arethetypeswrong/cli": "^0.15.4", | ||
"@arethetypeswrong/cli": "^0.17.1", | ||
"@repo/eslint-config": "*", | ||
"@repo/lint-staged-config": "*", | ||
"@repo/typescript-config": "*", | ||
"@types/react": "^18.3.3", | ||
"@types/react": "^19.0.1", | ||
"eslint": "^8.57.0", | ||
"publint": "^0.2.10", | ||
"react": "^18.3.1", | ||
"tsup": "^8.2.4", | ||
"typescript": "^5.5.4" | ||
"publint": "^0.2.12", | ||
"react": "^19.0.0", | ||
"tsup": "^8.3.5", | ||
"typescript": "^5.7.2" | ||
}, | ||
"peerDependencies": { | ||
"react": ">=16.8.0", | ||
"react-dom": ">=16.8.0" | ||
"react": ">=19", | ||
"react-dom": ">=19" | ||
} | ||
} |
@@ -13,3 +13,3 @@ # react-intersection-observer-hook | ||
This is a simple to use React hook package for using [Insersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) declaratively. By using this hook, you can easily track if a component is visible or not, create lazy loading images, trigger animations on entering or leaving the viewport, implement infinite loading etc. | ||
This is a easy to use React hook package for using [Insersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) declaratively. By using this hook, you can easily track if a component is visible or not, create lazy loading images, trigger animations on entering or leaving the viewport, implement infinite scroll etc. | ||
@@ -22,2 +22,8 @@ **Live demo is [here](https://onderonur.github.io/react-intersection-observer-hook).** | ||
## Versions | ||
For **React v19**, it is recommended to use versions after `v4`, since it uses [cleanup functions for refs](https://react.dev/blog/2024/12/05/react-19#cleanup-functions-for-refs). | ||
For older versions of React, you can stick with `v3` until you migrate to React 19. | ||
## Installation | ||
@@ -31,3 +37,3 @@ | ||
```javascript | ||
```jsx | ||
import React, { useEffect } from 'react'; | ||
@@ -44,7 +50,8 @@ import { useIntersectionObserver } from 'react-intersection-observer-hook'; | ||
useEffect(() => { | ||
console.log(`The component is ${isVisible ? 'visible' : 'not visible'}.`); | ||
}, [isVisible]); | ||
return <SomeComponentToTrack ref={ref} />; | ||
return ( | ||
<div> | ||
<p>Component is {isVisible ? 'visible' : 'not visible'}.</p> | ||
<SomeComponentToTrack ref={ref} /> | ||
</div> | ||
); | ||
} | ||
@@ -55,3 +62,3 @@ ``` | ||
```javascript | ||
```jsx | ||
import React, { useEffect } from 'react'; | ||
@@ -65,13 +72,12 @@ import { useIntersectionObserver } from 'react-intersection-observer-hook'; | ||
useEffect(() => { | ||
console.log(`The component is ${isVisible ? 'visible' : 'not visible'}.`); | ||
}, [isVisible]); | ||
return ( | ||
<ScrollableContainer | ||
// We use `rootRef` callback to set the root node. | ||
ref={rootRef} | ||
> | ||
<SomeComponentToTrack ref={ref} /> | ||
</ScrollableContainer> | ||
<div> | ||
<p>Component is {isVisible ? 'visible' : 'not visible'}.</p> | ||
<ScrollableContainer | ||
// We use `rootRef` callback to set the root node. | ||
ref={rootRef} | ||
> | ||
<SomeComponentToTrack ref={ref} /> | ||
</ScrollableContainer> | ||
</div> | ||
); | ||
@@ -81,6 +87,5 @@ } | ||
If you just want to track visibility, you can also use `useTrackVisibility` hook. | ||
It has the same API as `useIntersectionObserver` hook. It just returns additional fields as its second tuple item. | ||
If you just want to track visibility, you can also use `useTrackVisibility` hook. It mostly has the same API as `useIntersectionObserver` hook. | ||
```javascript | ||
```jsx | ||
import React, { useEffect } from 'react'; | ||
@@ -97,11 +102,14 @@ import { useTrackVisibility } from 'react-intersection-observer-hook'; | ||
// `isVisible`: Becomes `true`/`false` based on the response of `IntersectionObserver`. | ||
// `wasEverVisible`: When the observed node becomes visible once, this flag becomes `true` and stays like that. | ||
const [ref, { entry, rootRef, isVisible, wasEverVisible }] = | ||
useTrackVisibility(); | ||
const [ref, { entry, rootRef, isVisible }] = useTrackVisibility({ | ||
// In addition to the `IntersectionObserver` arguments, you can use `once` flag | ||
// to watch the visibility of an element once, so `isVisible` stays `true` after the element is visible for the first time. | ||
// once: true, | ||
}); | ||
useEffect(() => { | ||
console.log(`The component is ${isVisible ? 'visible' : 'not visible'}.`); | ||
}, [isVisible]); | ||
return <SomeComponentToTrack ref={ref} />; | ||
return ( | ||
<div> | ||
<p>Component is {isVisible ? 'visible' : 'not visible'}.</p> | ||
<SomeComponentToTrack ref={ref} /> | ||
</div> | ||
); | ||
} | ||
@@ -114,9 +122,15 @@ ``` | ||
Both `useIntersectionObserver` and `useTrackVisibility` gets the same arguments. And those are; | ||
### `useIntersectionObserver` | ||
- **rootMargin:** Indicates the margin value around the root element. Default value is zero for all directions (top, right, bottom and left). | ||
- **threshold:** Threshold value (or values) to trigger the observer. | ||
- `rootMargin`: Indicates the margin value around the root element. Default value is `0` for all directions (top, right, bottom and left). | ||
- `threshold`: Threshold value (or values) to trigger the observer. | ||
_For more info, you can check [here](https://developers.google.com/web/updates/2016/04/intersectionobserver) and [here](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)._ | ||
### useTrackVisibility | ||
Gets the same arguments as `useIntersectionObserver`. In addition: | ||
- `once`: When set `true`, `isVibisle` stays as `true` after the element is visible for the first time. Default `false`. | ||
## Contributors ✨ | ||
@@ -123,0 +137,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
20980
41.29%298
684.21%0
-100%157
9.79%1
Infinity%