Lazy loading defers loading images until they enter the viewport.
Native lazy loading:
1// Simplest approach2<img src="photo.jpg" loading="lazy" alt="Description" />
Intersection Observer hook:
1import { useState, useEffect, useRef } from "react";23function useLazyLoad(threshold = 0.1) {4 const [isVisible, setIsVisible] = useState(false);5 const ref = useRef<HTMLDivElement>(null);67 useEffect(() => {8 const observer = new IntersectionObserver(9 ([entry]) => {10 if (entry.isIntersecting) {11 setIsVisible(true);12 observer.disconnect();13 }14 },15 { threshold }16 );1718 if (ref.current) observer.observe(ref.current);19 return () => observer.disconnect();20 }, [threshold]);2122 return { ref, isVisible };23}2425function LazyImage({ src, alt }) {26 const { ref, isVisible } = useLazyLoad();2728 return (29 <div ref={ref} className="lazy-container">30 {isVisible ? (31 <img src={src} alt={alt} className="loaded" />32 ) : (33 <div className="placeholder" />34 )}35 </div>36 );37}
With blur placeholder:
1function LazyImage({ src, blurSrc, alt }) {2 const [loaded, setLoaded] = useState(false);3 const { ref, isVisible } = useLazyLoad();45 return (6 <div ref={ref} className="lazy-container">7 {isVisible && (8 <>9 <img10 src={blurSrc}11 alt=""12 className={`blur ${loaded ? "hidden" : ""}`}13 />14 <img15 src={src}16 alt={alt}17 className={loaded ? "loaded" : "hidden"}18 onLoad={() => setLoaded(true)}19 />20 </>21 )}22 </div>23 );24}
Benefits: