/** * LazyImage Component * Implements lazy loading for images using Intersection Observer * Only loads images when they're scrolled into view */ import { useState, useEffect, useRef } from 'react'; interface LazyImageProps { src: string; alt: string; className?: string; onLoad?: () => void; onError?: (e: React.SyntheticEvent) => void; } export function LazyImage({ src, alt, className = '', onLoad, onError }: LazyImageProps) { const [isLoaded, setIsLoaded] = useState(false); const [isInView, setIsInView] = useState(false); const [hasError, setHasError] = useState(false); const imgRef = useRef(null); useEffect(() => { if (!imgRef.current) return; const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setIsInView(true); observer.disconnect(); } }, { rootMargin: '100px', // Start loading 100px before visible threshold: 0.01, } ); observer.observe(imgRef.current); return () => observer.disconnect(); }, []); const handleLoad = () => { setIsLoaded(true); onLoad?.(); }; const handleError = (e: React.SyntheticEvent) => { setHasError(true); onError?.(e); }; return (
{!isInView || hasError ? ( // Loading skeleton or error state
) : ( {alt} )}
); } LazyImage.displayName = 'LazyImage';