Files
omniai-web/src/components/OptimizedImage.tsx
T
2026-06-02 12:38:01 +08:00

56 lines
1.5 KiB
TypeScript

import { useState, useCallback, type CSSProperties, type ImgHTMLAttributes } from "react";
const FALLBACK_SRC = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='80' viewBox='0 0 80 80'%3E%3Crect fill='%23222' width='80' height='80' rx='8'/%3E%3Cpath d='M28 52l8-12 6 8 12-16 10 20H28z' fill='%23444'/%3E%3Ccircle cx='32' cy='32' r='5' fill='%23444'/%3E%3C/svg%3E";
const baseStyle: CSSProperties = {
transition: "opacity 0.3s ease",
};
interface OptimizedImageProps extends ImgHTMLAttributes<HTMLImageElement> {
fallbackSrc?: string;
}
export default function OptimizedImage({
src,
alt = "",
fallbackSrc = FALLBACK_SRC,
style,
onLoad,
onError,
...rest
}: OptimizedImageProps) {
const [loaded, setLoaded] = useState(false);
const [errored, setErrored] = useState(false);
const handleLoad = useCallback(
(e: React.SyntheticEvent<HTMLImageElement>) => {
setLoaded(true);
onLoad?.(e);
},
[onLoad],
);
const handleError = useCallback(
(e: React.SyntheticEvent<HTMLImageElement>) => {
if (!errored) {
setErrored(true);
(e.target as HTMLImageElement).src = fallbackSrc;
}
onError?.(e);
},
[errored, fallbackSrc, onError],
);
return (
<img
src={src}
alt={alt}
loading="lazy"
style={{ ...baseStyle, opacity: loaded || errored ? 1 : 0, ...style }}
onLoad={handleLoad}
onError={handleError}
{...rest}
/>
);
}