@@ -0,0 +1,81 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState, type ImgHTMLAttributes } from "react";
|
||||
|
||||
import { useDecryptedSrc } from "@/hooks/use-decrypted-src";
|
||||
import { IMG_PLACEHOLDER } from "@/lib/image/decrypt";
|
||||
|
||||
type DecryptedImageProps = Omit<ImgHTMLAttributes<HTMLImageElement>, "src"> & {
|
||||
src?: string | null;
|
||||
fallback?: string;
|
||||
/** 首屏/关键图:立即解密,不懒加载 */
|
||||
priority?: boolean;
|
||||
/**
|
||||
* 视口外不发起解密请求。默认:非 priority 时开启。
|
||||
* 解密图无法靠原生 loading=lazy 省流量,必须在进视口后再 fetch。
|
||||
*/
|
||||
lazy?: boolean;
|
||||
/** IntersectionObserver rootMargin,提前预取(默认上下 320px) */
|
||||
lazyRootMargin?: string;
|
||||
};
|
||||
|
||||
export function DecryptedImage({
|
||||
src,
|
||||
fallback = IMG_PLACEHOLDER,
|
||||
priority = false,
|
||||
lazy,
|
||||
lazyRootMargin = "320px 0px",
|
||||
alt = "",
|
||||
onError,
|
||||
loading,
|
||||
decoding,
|
||||
fetchPriority,
|
||||
...props
|
||||
}: DecryptedImageProps) {
|
||||
const enableLazy = lazy ?? !priority;
|
||||
const imgRef = useRef<HTMLImageElement>(null);
|
||||
const [inView, setInView] = useState(!enableLazy);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enableLazy || inView) return;
|
||||
const node = imgRef.current;
|
||||
if (!node) return;
|
||||
|
||||
if (typeof IntersectionObserver === "undefined") {
|
||||
setInView(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const io = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries.some((entry) => entry.isIntersecting || entry.intersectionRatio > 0)) {
|
||||
setInView(true);
|
||||
io.disconnect();
|
||||
}
|
||||
},
|
||||
{ root: null, rootMargin: lazyRootMargin, threshold: 0 },
|
||||
);
|
||||
io.observe(node);
|
||||
return () => io.disconnect();
|
||||
}, [enableLazy, inView, lazyRootMargin]);
|
||||
|
||||
const resolved = useDecryptedSrc(src, { priority, fallback, enabled: inView });
|
||||
|
||||
return (
|
||||
<img
|
||||
{...props}
|
||||
ref={imgRef}
|
||||
alt={alt}
|
||||
src={resolved}
|
||||
loading={loading ?? (priority ? "eager" : "lazy")}
|
||||
decoding={decoding ?? (priority ? "sync" : "async")}
|
||||
fetchPriority={fetchPriority ?? (priority ? "high" : "auto")}
|
||||
onError={(event) => {
|
||||
if (event.currentTarget.src !== fallback) {
|
||||
event.currentTarget.src = fallback;
|
||||
}
|
||||
onError?.(event);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user