mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-09-05 07:36:53 +08:00
feat(image): implement image caching and preloading
This commit is contained in:
@@ -25,6 +25,14 @@ interface ImageViewerProps {
|
|||||||
onShare?: (image: PictureResponse) => void;
|
onShare?: (image: PictureResponse) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加图片缓存对象
|
||||||
|
interface ImageCache {
|
||||||
|
[key: string]: {
|
||||||
|
loaded: boolean;
|
||||||
|
img: HTMLImageElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const ImageViewer: React.FC<ImageViewerProps> = ({
|
const ImageViewer: React.FC<ImageViewerProps> = ({
|
||||||
visible,
|
visible,
|
||||||
onClose,
|
onClose,
|
||||||
@@ -45,28 +53,65 @@ const ImageViewer: React.FC<ImageViewerProps> = ({
|
|||||||
const [localImages, setLocalImages] = useState<PictureResponse[]>(images);
|
const [localImages, setLocalImages] = useState<PictureResponse[]>(images);
|
||||||
const [shareDialogVisible, setShareDialogVisible] = useState(false);
|
const [shareDialogVisible, setShareDialogVisible] = useState(false);
|
||||||
const [imageLoaded, setImageLoaded] = useState(false);
|
const [imageLoaded, setImageLoaded] = useState(false);
|
||||||
const preloadImageRef = useRef<HTMLImageElement | null>(null);
|
|
||||||
const [cacheKey, setCacheKey] = useState<number>(Date.now());
|
// 修改:使用 useRef 存储图片缓存,避免重复加载
|
||||||
|
const imageCache = useRef<ImageCache>({});
|
||||||
|
|
||||||
|
// 替换原来的 cacheKey
|
||||||
|
const sessionKey = useRef<string>(Date.now().toString());
|
||||||
|
|
||||||
const currentImage = localImages[currentIndex];
|
const currentImage = localImages[currentIndex];
|
||||||
|
|
||||||
|
// 预加载图片的范围
|
||||||
|
const preloadRange = 2; // 当前图片前后各预加载2张
|
||||||
|
|
||||||
const resetViewerState = useCallback(() => {
|
const resetViewerState = useCallback(() => {
|
||||||
setRotation(0);
|
setRotation(0);
|
||||||
setIsInfoDrawerOpen(false);
|
setIsInfoDrawerOpen(false);
|
||||||
setImageLoaded(false);
|
setImageLoaded(false);
|
||||||
setCacheKey(Date.now());
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 优化图片加载函数
|
||||||
|
const loadImage = useCallback((imageUrl: string): Promise<HTMLImageElement> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// 检查是否已经缓存
|
||||||
|
if (imageCache.current[imageUrl]?.loaded) {
|
||||||
|
setImageLoaded(true);
|
||||||
|
return resolve(imageCache.current[imageUrl].img);
|
||||||
|
}
|
||||||
|
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => {
|
||||||
|
// 更新缓存
|
||||||
|
imageCache.current[imageUrl] = {
|
||||||
|
loaded: true,
|
||||||
|
img
|
||||||
|
};
|
||||||
|
resolve(img);
|
||||||
|
};
|
||||||
|
img.onerror = () => {
|
||||||
|
reject(new Error(`Failed to load image: ${imageUrl}`));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 使用相对持久的缓存键,而不是每次都更新
|
||||||
|
img.src = `${imageUrl}${imageUrl.includes('?') ? '&' : '?'}_s=${sessionKey.current}`;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 修改:当图片索引变化时只设置加载状态,不重置缓存键
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setImageLoaded(false);
|
setImageLoaded(false);
|
||||||
setCacheKey(Date.now());
|
|
||||||
}, [currentIndex]);
|
}, [currentIndex]);
|
||||||
|
|
||||||
|
// 修改:查看器可见性改变时的处理逻辑
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible && !wasVisible.current) {
|
if (visible && !wasVisible.current) {
|
||||||
resetViewerState();
|
resetViewerState();
|
||||||
} else if (!visible && wasVisible.current) {
|
|
||||||
setTimeout(() => setCacheKey(Date.now()), 300);
|
// 生成会话唯一的缓存键,而不是每次都更新
|
||||||
|
if (!sessionKey.current) {
|
||||||
|
sessionKey.current = Date.now().toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
wasVisible.current = visible;
|
wasVisible.current = visible;
|
||||||
}, [visible, resetViewerState]);
|
}, [visible, resetViewerState]);
|
||||||
@@ -77,24 +122,39 @@ const ImageViewer: React.FC<ImageViewerProps> = ({
|
|||||||
}
|
}
|
||||||
}, [visible, initialIndex, images.length]);
|
}, [visible, initialIndex, images.length]);
|
||||||
|
|
||||||
|
// 修改:加载当前图片并预加载相邻图片
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentImage) {
|
if (!currentImage || !visible) return;
|
||||||
const img = new Image();
|
|
||||||
img.onload = () => {
|
|
||||||
setImageLoaded(true);
|
|
||||||
preloadImageRef.current = img;
|
|
||||||
};
|
|
||||||
const cacheBuster = `${currentImage.path}${currentImage.path.includes('?') ? '&' : '?'}_cb=${cacheKey}`;
|
|
||||||
img.src = cacheBuster;
|
|
||||||
|
|
||||||
return () => {
|
// 加载当前图片
|
||||||
img.onload = null;
|
const imagePath = currentImage.path;
|
||||||
if (preloadImageRef.current === img) {
|
loadImage(imagePath)
|
||||||
preloadImageRef.current = null;
|
.then(() => setImageLoaded(true))
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Failed to load image:', error);
|
||||||
|
message.error('图片加载失败,请重试');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 预加载相邻图片
|
||||||
|
if (localImages.length > 1) {
|
||||||
|
// 使用 setTimeout 延迟预加载,优先加载当前图片
|
||||||
|
setTimeout(() => {
|
||||||
|
for (let i = 1; i <= preloadRange; i++) {
|
||||||
|
// 预加载后面的图片
|
||||||
|
const nextIndex = currentIndex + i;
|
||||||
|
if (nextIndex < localImages.length) {
|
||||||
|
loadImage(localImages[nextIndex].path).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预加载前面的图片
|
||||||
|
const prevIndex = currentIndex - i;
|
||||||
|
if (prevIndex >= 0) {
|
||||||
|
loadImage(localImages[prevIndex].path).catch(() => {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
}, 300);
|
||||||
}
|
}
|
||||||
}, [currentImage, cacheKey]);
|
}, [currentImage, visible, currentIndex, localImages, loadImage]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLocalImages(images);
|
setLocalImages(images);
|
||||||
@@ -213,6 +273,11 @@ const ImageViewer: React.FC<ImageViewerProps> = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 修改:更新图片URL,使用会话缓存键
|
||||||
|
const getImageUrl = (path: string) => {
|
||||||
|
return `${path}${path.includes('?') ? '&' : '?'}_s=${sessionKey.current}`;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`image-viewer-container ${visible ? 'visible' : ''}`}
|
className={`image-viewer-container ${visible ? 'visible' : ''}`}
|
||||||
@@ -243,7 +308,7 @@ const ImageViewer: React.FC<ImageViewerProps> = ({
|
|||||||
>
|
>
|
||||||
{currentImage && (
|
{currentImage && (
|
||||||
<img
|
<img
|
||||||
src={`${currentImage.path}${currentImage.path.includes('?') ? '&' : '?'}_cb=${cacheKey}`}
|
src={getImageUrl(currentImage.path)}
|
||||||
alt={currentImage.name}
|
alt={currentImage.name}
|
||||||
style={{
|
style={{
|
||||||
transform: `rotate(${rotation}deg)`,
|
transform: `rotate(${rotation}deg)`,
|
||||||
|
|||||||
Reference in New Issue
Block a user