75 lines
2.4 KiB
TypeScript
75 lines
2.4 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState, type CSSProperties, type MouseEventHandler } from "react";
|
|
import { buildImageProxyUrl, getFullscreenPreviewSize } from "@/lib/image-delivery";
|
|
|
|
export type LoadedImagePreview = { src: string; width: number; height: number };
|
|
|
|
/** Keep the already displayed image visible while its larger preview loads. */
|
|
export function ProgressiveImagePreview({
|
|
src,
|
|
preview,
|
|
alt,
|
|
className,
|
|
style,
|
|
onClick,
|
|
}: {
|
|
src: string;
|
|
preview: LoadedImagePreview;
|
|
alt: string;
|
|
className?: string;
|
|
style?: CSSProperties;
|
|
onClick?: MouseEventHandler<HTMLImageElement>;
|
|
}) {
|
|
const size = getFullscreenPreviewSize({
|
|
imageWidth: preview.width,
|
|
imageHeight: preview.height,
|
|
viewportWidth: typeof window === "undefined" ? 1280 : window.innerWidth,
|
|
viewportHeight: typeof window === "undefined" ? 900 : window.innerHeight,
|
|
pixelRatio: typeof window === "undefined" ? 1 : window.devicePixelRatio,
|
|
});
|
|
const fullSrc = buildImageProxyUrl(src, {
|
|
delivery: "proxy",
|
|
width: size.requestWidth,
|
|
quality: 86,
|
|
format: "webp",
|
|
});
|
|
const [loadedSrc, setLoadedSrc] = useState("");
|
|
const [failedSrc, setFailedSrc] = useState("");
|
|
const ready = fullSrc === preview.src || loadedSrc === fullSrc;
|
|
const failed = failedSrc === fullSrc;
|
|
|
|
useEffect(() => {
|
|
if (fullSrc === preview.src) return;
|
|
const image = new Image();
|
|
image.decoding = "async";
|
|
image.onload = () => setLoadedSrc(fullSrc);
|
|
image.onerror = () => setFailedSrc(fullSrc);
|
|
image.src = fullSrc;
|
|
return () => {
|
|
image.onload = null;
|
|
image.onerror = null;
|
|
image.removeAttribute("src");
|
|
};
|
|
}, [fullSrc, preview.src]);
|
|
|
|
return (
|
|
<div className="relative flex items-center justify-center" onClick={(event) => event.stopPropagation()}>
|
|
<img
|
|
alt={alt}
|
|
className={className}
|
|
decoding="async"
|
|
draggable={false}
|
|
onClick={onClick}
|
|
src={ready ? fullSrc : preview.src}
|
|
style={{ width: size.displayWidth, ...style }}
|
|
/>
|
|
{!ready && (
|
|
<div role="status" className="pointer-events-none absolute bottom-4 max-w-full rounded-lg bg-black/80 px-3 py-2 text-center text-sm text-white">
|
|
{failed ? "高清预览加载失败,已保留当前预览;可关闭后重试或下载原图" : "正在加载高清预览…"}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|