-
Notifications
You must be signed in to change notification settings - Fork 0
useIntersectionObserver
Detects whether a specific DOM element is currently visible in the user's viewport. It is highly optimized for performance and is ideal for lazy-loading images, triggering infinite scrolling, or firing entrance animations without janky scroll-event listeners.
Detecting if a footer element has entered the screen.
import { useRef } from "react";
import { useIntersectionObserver } from "react-hook-lab";
function LazyFooter() {
const footerRef = useRef<HTMLDivElement>(null);
const { isIntersecting } = useIntersectionObserver(footerRef);
return (
<div ref={footerRef} style={{ height: 200, background: isIntersecting ? "green" : "gray" }}>
{isIntersecting ? "I am visible!" : "Scroll down to see me"}
</div>
);
}Using freezeOnceVisible for lazy loading images or entrance animations. Once the element appears on screen, it stays "visible" forever, stopping the observer and saving CPU cycles.
import { useRef } from "react";
import { useIntersectionObserver } from "react-hook-lab";
function FadeInSection({ children }: { children: React.ReactNode }) {
const sectionRef = useRef<HTMLDivElement>(null);
// Triggers when 20% of the element is visible, and then freezes
const { isIntersecting } = useIntersectionObserver(sectionRef, {
threshold: 0.2,
freezeOnceVisible: true,
});
return (
<div
ref={sectionRef}
style={{
opacity: isIntersecting ? 1 : 0,
transform: isIntersecting ? "translateY(0)" : "translateY(50px)",
transition: "all 0.6s ease-out"
}}
>
{children}
</div>
);
}Using rootMargin to preemptively load data before the user actually scrolls to the element (perfect for infinite scroll).
import { useRef, useEffect } from "react";
import { useIntersectionObserver } from "react-hook-lab";
function InfiniteFeed({ loadMore }: { loadMore: () => void }) {
const loaderRef = useRef<HTMLDivElement>(null);
// Triggers when the element is 500px AWAY from entering the screen
const { isIntersecting } = useIntersectionObserver(loaderRef, {
rootMargin: "500px 0px"
});
useEffect(() => {
if (isIntersecting) {
loadMore();
}
}, [isIntersecting, loadMore]);
return (
<div>
{/* ... Feed Items ... */}
<div ref={loaderRef}>Loading more posts...</div>
</div>
);
}-
ref- Type:
RefObject<HTMLElement> - Description: The React ref attached to the DOM element you want to observe.
- Type:
-
options(Optional)- Type:
UseIntersectionObserverOptions - Description: Configuration object extending standard
IntersectionObserverInit.
- Type:
export interface UseIntersectionObserverOptions extends IntersectionObserverInit {
freezeOnceVisible?: boolean; // If true, disconnects observer after first intersection
}Returns an object of type UseIntersectionObserverReturn containing:
-
isIntersecting(boolean): A strict boolean representing if the element is currently visible (based on yourthreshold). -
entry(IntersectionObserverEntry | undefined): The raw, underlying Intersection Observer entry containing detailed metrics likeboundingClientRect,intersectionRatio, etc.
Under the hood, useIntersectionObserver utilizes the native browser IntersectionObserver API.
When the component mounts, it creates a new observer instance targeted at ref.current. When the element's visibility crosses the specified threshold, the browser fires a callback that updates the React state with the new IntersectionObserverEntry.
If freezeOnceVisible: true is passed, the hook actively checks if (entry?.isIntersecting). The moment this becomes true, the useEffect cleans up, calls observer.disconnect(), and stops observing entirely. This ensures that entrance animations or lazy-loaders consume zero performance after their initial trigger.
Built with ❤️ by Saurav-TB-Pandey. | Report an Issue | NPM