ai-chatbot/components/image-delivery-optimizer.tsx
Your Name a7b9533d72
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
perf: optimize oss image delivery globally
2026-04-29 22:32:06 +08:00

124 lines
3.2 KiB
TypeScript

"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";
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 optimizeImageElement(image: HTMLImageElement): void {
if (image.dataset.imageDelivery === "raw" || image.dataset.imageDelivery === "proxy") {
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 optimizeAllImages(root: ParentNode = document): void {
root.querySelectorAll("img").forEach((image) => {
optimizeImageElement(image);
});
}
export function ImageDeliveryOptimizer() {
useEffect(() => {
let frameId = 0;
const scheduleOptimize = (root: ParentNode = document) => {
if (frameId) {
cancelAnimationFrame(frameId);
}
frameId = requestAnimationFrame(() => {
frameId = 0;
optimizeAllImages(root);
});
};
scheduleOptimize();
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === "attributes") {
const target = mutation.target;
if (target instanceof HTMLImageElement) {
optimizeImageElement(target);
}
continue;
}
for (const node of mutation.addedNodes) {
if (node instanceof HTMLImageElement) {
optimizeImageElement(node);
} 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 () => {
if (frameId) {
cancelAnimationFrame(frameId);
}
observer.disconnect();
window.removeEventListener("resize", handleResize);
};
}, []);
return null;
}