"use client"; import { useEffect } from "react"; import { buildImageProxyUrl } from "@/lib/image-delivery"; const MIN_DISPLAY_WIDTH = 160; const DEFAULT_DISPLAY_WIDTH = 960; const MAX_DISPLAY_WIDTH = 2200; const OPTIMIZED_MARK = "ossDisplayOptimized"; const MANUAL_SCOPE_ATTR = "data-image-delivery-scope"; const BATCH_SIZE = 48; type IdleDeadlineLike = { timeRemaining: () => number; }; type IdleWindow = Window & { requestIdleCallback?: ( callback: (deadline: IdleDeadlineLike) => void, options?: { timeout?: number } ) => number; cancelIdleCallback?: (id: number) => void; }; function isSkippableImageUrl(url: string): boolean { const normalized = url.trim().toLowerCase(); return ( !normalized || normalized.startsWith("data:") || normalized.startsWith("blob:") || normalized.startsWith("/api/image-proxy") || normalized.includes("x-oss-process=") || /\.(svg|gif|ico)(?:[?#]|$)/i.test(normalized) ); } function getDisplayWidth(image: HTMLImageElement): number { const rectWidth = image.getBoundingClientRect().width; const width = rectWidth || image.clientWidth || Number.parseInt(image.getAttribute("width") || "", 10) || DEFAULT_DISPLAY_WIDTH; const dpr = Math.min(2, Math.max(1, window.devicePixelRatio || 1)); return Math.round(Math.max(MIN_DISPLAY_WIDTH, Math.min(MAX_DISPLAY_WIDTH, width * dpr))); } function isInManualDeliveryScope(image: HTMLImageElement): boolean { if ( image.dataset.imageDelivery === "raw" || image.dataset.imageDelivery === "proxy" || image.dataset.imageDelivery === "manual" ) { return true; } return Boolean(image.closest(`[${MANUAL_SCOPE_ATTR}="manual"]`)); } function optimizeImageElement(image: HTMLImageElement): void { if (isInManualDeliveryScope(image)) { return; } const currentSrc = image.currentSrc || image.src || image.getAttribute("src") || ""; if (isSkippableImageUrl(currentSrc)) { return; } const optimizedUrl = buildImageProxyUrl(currentSrc, { width: getDisplayWidth(image), quality: 84, format: "webp", }); if ( !optimizedUrl || optimizedUrl === currentSrc || optimizedUrl.startsWith("/api/image-proxy") ) { return; } image.dataset[OPTIMIZED_MARK] = "1"; image.src = optimizedUrl; } function collectImages(root: ParentNode = document): HTMLImageElement[] { if (root instanceof HTMLImageElement) { return [root]; } if (root instanceof Element && root.closest(`[${MANUAL_SCOPE_ATTR}="manual"]`)) { return []; } return Array.from(root.querySelectorAll("img")); } export function ImageDeliveryOptimizer() { useEffect(() => { const pendingImages = new Set(); const idleWindow = window as IdleWindow; let scheduledId = 0; let scheduledWithIdle = false; const cancelScheduledFlush = () => { if (!scheduledId) return; if (scheduledWithIdle && typeof idleWindow.cancelIdleCallback === "function") { idleWindow.cancelIdleCallback(scheduledId); } else { cancelAnimationFrame(scheduledId); } scheduledId = 0; scheduledWithIdle = false; }; const scheduleFlush = () => { if (scheduledId) return; const flush = (deadline?: IdleDeadlineLike) => { scheduledId = 0; scheduledWithIdle = false; let processed = 0; for (const image of Array.from(pendingImages)) { pendingImages.delete(image); if (image.isConnected) { optimizeImageElement(image); } processed += 1; if ( processed >= BATCH_SIZE || (deadline && deadline.timeRemaining() < 4) ) { break; } } if (pendingImages.size > 0) { scheduleFlush(); } }; if (typeof idleWindow.requestIdleCallback === "function") { scheduledWithIdle = true; scheduledId = idleWindow.requestIdleCallback(flush, { timeout: 500 }); return; } scheduledId = requestAnimationFrame(() => { flush({ timeRemaining: () => 8 }); }); }; const scheduleOptimize = (root: ParentNode = document) => { for (const image of collectImages(root)) { pendingImages.add(image); } if (pendingImages.size > 0) { scheduleFlush(); } }; scheduleOptimize(); const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type === "attributes") { const target = mutation.target; if (target instanceof HTMLImageElement) { pendingImages.add(target); scheduleFlush(); } continue; } for (const node of mutation.addedNodes) { if (node instanceof HTMLImageElement) { pendingImages.add(node); scheduleFlush(); } else if (node instanceof Element) { scheduleOptimize(node); } } } }); observer.observe(document.body, { attributes: true, attributeFilter: ["src"], childList: true, subtree: true, }); const handleResize = () => scheduleOptimize(); window.addEventListener("resize", handleResize, { passive: true }); return () => { cancelScheduledFlush(); observer.disconnect(); window.removeEventListener("resize", handleResize); }; }, []); return null; }