perf(canvas): 优化画布与资源多级缓存,支持 SWR 预读、直接图片流式呈现与浏览器强缓存
This commit is contained in:
@@ -15,7 +15,7 @@ import type { CanvasTheme } from "@/lib/canvas-theme";
|
||||
import { formatBytes } from "@/lib/image-utils";
|
||||
import { resourceIdFromStorageKey } from "@/services/api/resources";
|
||||
import type { GenerationTask } from "@/services/api/task-center";
|
||||
import { cacheResourceObjectUrl, getCachedResourceObjectUrl, scheduleResourceBlobCache } from "@/services/resource-blob-cache";
|
||||
import { cacheResourceObjectUrl, getCachedResourceObjectUrl, peekCachedResourceObjectUrl, scheduleResourceBlobCache } from "@/services/resource-blob-cache";
|
||||
import { resolveMediaUrl } from "@/services/file-storage";
|
||||
import { hydrateCanvasVideoPreview } from "@/services/canvas-video-preview";
|
||||
import { CanvasNodeType, type CanvasNodeData } from "@/types/canvas";
|
||||
@@ -655,8 +655,13 @@ function useNodeResourceUrl(node: CanvasNodeData, eager: boolean) {
|
||||
// still expensive. Images must wait for the same viewport gate as remote
|
||||
// resources; otherwise DOM virtualization does not reduce image work.
|
||||
const isLazyVisual = node.type === CanvasNodeType.Image;
|
||||
const [url, setUrl] = useState(isRemoteResource || isLazyVisual ? "" : fallback);
|
||||
const [loading, setLoading] = useState(isRemoteResource && eager);
|
||||
const synchronousUrl = isRemoteResource ? peekCachedResourceObjectUrl(storageKey) : "";
|
||||
const isHttpUrl = Boolean(fallback && !fallback.startsWith("data:"));
|
||||
// 优先使用内存中的有效 Blob URL;若已进入视口且有服务端的直接图片地址(非 data: base64),
|
||||
// 立即作为初始图片呈现,利用浏览器原生 HTTP 磁盘缓存秒开,消除满屏转圈等待。
|
||||
const initialUrl = synchronousUrl || (eager && isHttpUrl ? fallback : (isRemoteResource || isLazyVisual ? "" : fallback));
|
||||
const [url, setUrl] = useState(() => initialUrl);
|
||||
const [loading, setLoading] = useState(() => !initialUrl && isRemoteResource && eager);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
@@ -665,19 +670,30 @@ function useNodeResourceUrl(node: CanvasNodeData, eager: boolean) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
setUrl("");
|
||||
setLoading(eager);
|
||||
const cachedSync = peekCachedResourceObjectUrl(storageKey);
|
||||
if (cachedSync) {
|
||||
setUrl(cachedSync);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
if (!url && eager && isHttpUrl) {
|
||||
setUrl(fallback);
|
||||
setLoading(false);
|
||||
} else if (!url) {
|
||||
setLoading(eager);
|
||||
}
|
||||
// 只有进入视口或被激活的节点才下载远程媒体;缓存层会复用已有 Blob URL 和 in-flight 请求。
|
||||
const resolve = eager ? cacheResourceObjectUrl(storageKey) : getCachedResourceObjectUrl(storageKey);
|
||||
void resolve.then((cached) => {
|
||||
if (!cancelled) setUrl(cached || (eager ? fallback : ""));
|
||||
if (!cancelled && cached) setUrl(cached);
|
||||
else if (!cancelled && eager && fallback) setUrl(fallback);
|
||||
}).catch(() => {
|
||||
if (!cancelled && eager) setUrl(fallback);
|
||||
}).finally(() => {
|
||||
if (!cancelled) setLoading(false);
|
||||
});
|
||||
return () => { cancelled = true; };
|
||||
}, [eager, fallback, isLazyVisual, isRemoteResource, storageKey]);
|
||||
}, [eager, fallback, isHttpUrl, isLazyVisual, isRemoteResource, storageKey]);
|
||||
|
||||
return { url, loading };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user