@akson/cortex-landing-hooks
Version:
React hooks for landing pages - device detection, API calls, form submission, analytics, and performance
175 lines (173 loc) • 5.03 kB
JavaScript
// src/performance/useImagePreloader.ts
import { useCallback, useEffect, useState, useRef } from "react";
function useImagePreloader(options) {
const {
images,
preloadDelay = 100,
initialDelay = 500,
concurrency = 3,
onComplete,
onError,
onLoad
} = options;
const [loadedImages, setLoadedImages] = useState(/* @__PURE__ */ new Set());
const [failedImages, setFailedImages] = useState(/* @__PURE__ */ new Set());
const [isPreloading, setIsPreloading] = useState(false);
const progress = images.length > 0 ? Math.round((loadedImages.size + failedImages.size) / images.length * 100) : 0;
const preloadImage = useCallback(async (src) => {
return new Promise((resolve, reject) => {
if (loadedImages.has(src)) {
resolve();
return;
}
const img = new Image();
img.onload = () => {
setLoadedImages((prev) => {
const newSet = new Set(prev);
newSet.add(src);
return newSet;
});
if (onLoad) {
onLoad(src);
}
resolve();
};
img.onerror = () => {
const error = new Error(`Failed to load image: ${src}`);
setFailedImages((prev) => {
const newSet = new Set(prev);
newSet.add(src);
return newSet;
});
if (onError) {
onError(src, error);
}
resolve();
};
img.src = src;
});
}, [loadedImages, onLoad, onError]);
const preloadWithConcurrency = useCallback(async (imagesToPreload) => {
if (imagesToPreload.length === 0) return;
const chunks = [];
for (let i = 0; i < imagesToPreload.length; i += concurrency) {
chunks.push(imagesToPreload.slice(i, i + concurrency));
}
for (const chunk of chunks) {
await Promise.all(chunk.map((src) => preloadImage(src)));
if (preloadDelay > 0 && chunk !== chunks[chunks.length - 1]) {
await new Promise((resolve) => setTimeout(resolve, preloadDelay));
}
}
}, [concurrency, preloadDelay, preloadImage]);
const preloadAll = useCallback(async () => {
if (images.length === 0) return;
const imagesToPreload = images.filter(
(src) => !loadedImages.has(src) && !failedImages.has(src)
);
if (imagesToPreload.length === 0) return;
setIsPreloading(true);
try {
await preloadWithConcurrency(imagesToPreload);
} finally {
setIsPreloading(false);
if (onComplete) {
onComplete();
}
}
}, [images, loadedImages, failedImages, preloadWithConcurrency, onComplete]);
useEffect(() => {
if (images.length === 0) return;
let cancelled = false;
let timeoutId;
const startPreloading = async () => {
if (cancelled) return;
await preloadAll();
};
timeoutId = setTimeout(startPreloading, initialDelay);
return () => {
cancelled = true;
clearTimeout(timeoutId);
};
}, [images, initialDelay, preloadAll]);
const reset = useCallback(() => {
setLoadedImages(/* @__PURE__ */ new Set());
setFailedImages(/* @__PURE__ */ new Set());
setIsPreloading(false);
}, []);
return {
loadedImages,
failedImages,
isPreloading,
progress,
preloadImage,
preloadAll,
reset
};
}
function useLazyImage(imageSrc, options = {}) {
const {
placeholder = "",
rootMargin = "50px",
threshold = 0.1,
onLoadStart,
onLoad,
onError
} = options;
const [src, setSrc] = useState(placeholder);
const [isLoading, setIsLoading] = useState(false);
const [hasLoaded, setHasLoaded] = useState(false);
const [hasError, setHasError] = useState(false);
const imgRef = useRef(null);
useEffect(() => {
if (!imgRef.current || typeof IntersectionObserver === "undefined") {
return;
}
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && !hasLoaded && !isLoading) {
setIsLoading(true);
if (onLoadStart) {
onLoadStart();
}
const img = new Image();
img.onload = () => {
setSrc(imageSrc);
setHasLoaded(true);
setIsLoading(false);
if (onLoad) {
onLoad();
}
};
img.onerror = () => {
const error = new Error(`Failed to load image: ${imageSrc}`);
setHasError(true);
setIsLoading(false);
if (onError) {
onError(error);
}
};
img.src = imageSrc;
}
});
},
{ rootMargin, threshold }
);
observer.observe(imgRef.current);
return () => {
observer.disconnect();
};
}, [imageSrc, rootMargin, threshold, hasLoaded, isLoading, onLoadStart, onLoad, onError]);
return {
src,
isLoading,
hasLoaded,
hasError,
ref: imgRef
};
}
export {
useImagePreloader,
useLazyImage
};