UNPKG

react-lazy-img-observer

Version:

A React component for lazy loading images with Intersection Observer

88 lines (87 loc) 4.05 kB
"use client"; import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; import { useEffect, useRef, useState, useMemo } from "react"; const ImageLazy = ({ src, alt, srcSet, sizes, width, height, className, id, extraData, title, useTitleFromAlt = false, viewTransitionName, style, backgroundColor, animationDuration = "0.9s", blurAmount = "20px", fallbackSrc, threshold = 0.1, transitionType = "blur", onLoadComplete, visibleByDefault = false, loadingComponent, }) => { const imageRef = useRef(null); const [realSrc, setRealSrc] = useState(visibleByDefault ? src : null); const [loaded, setLoaded] = useState(false); const [hasError, setHasError] = useState(false); useEffect(() => { setLoaded(false); setHasError(false); setRealSrc(visibleByDefault ? src : null); }, [src, visibleByDefault]); useEffect(() => { const isDev = typeof window !== "undefined" && window?.location?.hostname?.includes("localhost"); if (isDev) { if (!alt) { console.warn("[ImageLazy] ⚠️ Se recomienda definir el atributo 'alt' por accesibilidad."); } if (!src) { console.warn("[ImageLazy] ⚠️ La prop 'src' está vacía o nula. La imagen no se cargará."); } } }, [alt, src]); useEffect(() => { if (typeof window === "undefined" || visibleByDefault) return; if (typeof IntersectionObserver === "undefined" || !imageRef.current) return; const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { setRealSrc(src); observer.unobserve(entry.target); } }); }, { root: null, rootMargin: "0px", threshold }); observer.observe(imageRef.current); return () => observer.disconnect(); }, [src, threshold, visibleByDefault, realSrc]); const handleLoad = () => { setLoaded(true); onLoadComplete?.(); }; const handleError = () => { if (fallbackSrc && realSrc !== fallbackSrc) { setRealSrc(fallbackSrc); } else { setHasError(true); } }; const isCustom = transitionType === "custom"; const transitionStyles = useMemo(() => { if (isCustom) return {}; if (transitionType === "fade") { return { opacity: loaded ? 1 : 0, transition: `opacity ${animationDuration}`, }; } if (transitionType === "scale") { return { transform: loaded ? "scale(1)" : "scale(1.05)", opacity: loaded ? 1 : 0, transition: `transform ${animationDuration}, opacity ${animationDuration}`, }; } return { filter: loaded ? "none" : `blur(${blurAmount})`, transition: `filter ${animationDuration}`, }; }, [transitionType, loaded, animationDuration, blurAmount, isCustom]); const finalStyle = useMemo(() => ({ backgroundColor: !loaded && backgroundColor ? backgroundColor : undefined, backgroundSize: "cover", backgroundPosition: "center", ...(viewTransitionName ? { viewTransitionName } : {}), ...transitionStyles, ...style, }), [loaded, backgroundColor, viewTransitionName, transitionStyles, style]); return (_jsxs(_Fragment, { children: [!loaded && loadingComponent, (!hasError || realSrc === null) && (_jsx("img", { ref: imageRef, src: realSrc ?? undefined, alt: alt, title: title ?? (useTitleFromAlt ? alt : undefined), className: className, width: width, height: height, id: id?.toString(), onLoad: handleLoad, onError: handleError, srcSet: realSrc ? srcSet : undefined, sizes: realSrc ? sizes : undefined, style: finalStyle, ...extraData }))] })); }; export default ImageLazy; export { ImageLazy };