UNPKG

@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
// 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 };