ecom / client /src /components /ui /lazy-image.tsx
shashwatIDR's picture
Upload 106 files
1684141 verified
import { useState, useRef, useEffect } from "react";
import { cn } from "@/lib/utils";
interface LazyImageProps {
src: string;
alt: string;
className?: string;
placeholder?: string;
onLoad?: () => void;
onError?: () => void;
}
export default function LazyImage({
src,
alt,
className,
placeholder = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjQwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2YzZjRmNiIvPgo8dGV4dCB4PSI1MCUiIHk9IjUwJSIgZm9udC1zaXplPSIxOCIgZmlsbD0iIzlDQTNCRiIgZHk9Ii4zZW0iIHRleHQtYW5jaG9yPSJtaWRkbGUiPkxvYWRpbmcuLi48L3RleHQ+Cjwvc3ZnPg==",
onLoad,
onError
}: LazyImageProps) {
const [isLoaded, setIsLoaded] = useState(false);
const [isInView, setIsInView] = useState(false);
const [hasError, setHasError] = useState(false);
const imgRef = useRef<HTMLImageElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsInView(true);
observer.disconnect();
}
},
{ threshold: 0.1, rootMargin: '50px' }
);
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, []);
const handleLoad = () => {
setIsLoaded(true);
onLoad?.();
};
const handleError = () => {
setHasError(true);
onError?.();
};
return (
<div ref={imgRef} className={cn("overflow-hidden bg-gray-100", className)}>
<img
src={isInView ? src : placeholder}
alt={alt}
onLoad={handleLoad}
onError={handleError}
loading="lazy"
className={cn(
"transition-opacity duration-300 w-full h-full object-cover",
isLoaded ? "opacity-100" : "opacity-70",
hasError ? "opacity-50" : ""
)}
aria-label={alt}
/>
</div>
);
}