Using Intersection Observer for Lazy Loading and Scroll-Based Animations
The Intersection Observer API lets you efficiently detect when an element enters or leaves the viewport without costly scroll events. This is ideal for lazyāloading images, triggering animations, or loading infiniteāscroll content. By observing a target element, you get a callback with its intersection ratio, letting you decide when to act. For lazy loading, swap a placeholderās src with the real image once the element is about to enter the viewport. For animations, add a class that starts a CSS animation when the element becomes visible. The API works in all modern browsers and can be polyfilled for older ones. Use a rootMargin to start loading slightly before the element is fully visible, improving perceived performance. Remember to disconnect observers when components unmount to avoid memory leaks. This pattern reduces initial page weight, improves Core Web Vitals (especially LCP), and keeps interactions smooth without jank from scrollābased throttling.
š»Source Code
// hooks/useIntersection.js
import { useEffect, useState } from "react";
function useIntersectionObserver(options = {}) {
const [entry, setEntry] = useState(null);
const [node, setNode] = useState(null);
useEffect(() => {
if (!node) return;
const observer = new IntersectionObserver(([obsEntry]) => {
setEntry(obsEntry);
}, options);
observer.observe(node);
return () => observer.disconnect();
}, [node, options]);
return [setNode, entry];
}
// ImageLazy.jsx ā lazyāload an image when itās near the viewport
import { useIntersectionObserver } from "./hooks/useIntersection";
export default function ImageLazy({ src, alt, placeholder = "/placeholder.png" }) {
const [setRef, entry] = useIntersectionObserver({
rootMargin: "200px", // start loading 200px before itās visible
threshold: 0,
});
const [loaded, setLoaded] = useState(false);
// When the image starts to intersect, load the real src
useEffect(() => {
if (entry && entry.isIntersecting) {
setLoaded(true);
}
}, [entry]);
return (
<div>
<img
ref={setRef}
src={loaded ? src : placeholder}
alt={alt}
style={{ display: "block", maxWidth: "100%", height: "auto" }}
loading="lazy"
/>
{!loaded && (
<div
style={{
width: "100%",
paddingTop: "56.25%", // 16:9 placeholder
background: "#eee",
}}
/>
)}
</div>
);
}
// FadeInOnScroll.jsx ā animate elements as they enter the viewport
import { useIntersectionObserver } from "./hooks/useIntersection";
export default function FadeInOnScroll({ children }) {
const [setRef, entry] = useIntersectionObserver({
threshold: 0.1, // trigger when 10% visible
});
const [visible, setVisible] = useState(false);
useEffect(() => {
if (entry && entry.isIntersecting) {
setVisible(true);
}
}, [entry]);
return (
<div ref={setRef} className={visible ? "fade-in" : ""}>
{children}
</div>
);
}
// CSS for the animation (add to your stylesheet)
/*
.fade-in {
opacity: 0;
transform: translateY(20px);
animation: fadeInUp 0.6s ease-out forwards;
}
@keyframes fadeInUp {
to {
opacity: 1;
transform: translateY(0);
}
}
*/šRelated Snippets
Similar code snippets you might find interesting
š¬Comments (0)
šPlease login to post comments
No comments yet. Be the first to share your thoughts!
ā”Actions
Share this snippet:
š¤About the Author
manish
Active contributor
