react-lazy-img-observer
Version:
A React component for lazy loading images with Intersection Observer
91 lines (90 loc) • 4.27 kB
JavaScript
;
"use client";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ImageLazy = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("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 = (0, react_1.useRef)(null);
const [realSrc, setRealSrc] = (0, react_1.useState)(visibleByDefault ? src : null);
const [loaded, setLoaded] = (0, react_1.useState)(false);
const [hasError, setHasError] = (0, react_1.useState)(false);
(0, react_1.useEffect)(() => {
setLoaded(false);
setHasError(false);
setRealSrc(visibleByDefault ? src : null);
}, [src, visibleByDefault]);
(0, react_1.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]);
(0, react_1.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 = (0, react_1.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 = (0, react_1.useMemo)(() => ({
backgroundColor: !loaded && backgroundColor ? backgroundColor : undefined,
backgroundSize: "cover",
backgroundPosition: "center",
...(viewTransitionName ? { viewTransitionName } : {}),
...transitionStyles,
...style,
}), [loaded, backgroundColor, viewTransitionName, transitionStyles, style]);
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [!loaded && loadingComponent, (!hasError || realSrc === null) && ((0, jsx_runtime_1.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 }))] }));
};
exports.ImageLazy = ImageLazy;
exports.default = ImageLazy;