|
|
@@ -111,6 +111,8 @@ const animationCompleted = ref(false); // 新增:标记动画是否完成
|
|
|
const hotMode = ref(0);
|
|
|
const showPreview = ref(false);
|
|
|
const previewPath = `${import.meta.env.BASE_URL}images/2000/钱选白莲图.png`;
|
|
|
+const spriteUrl = ref(Img);
|
|
|
+let objectUrlRef = null;
|
|
|
|
|
|
// 序列帧参数
|
|
|
const SPRITE_CONFIG = {
|
|
|
@@ -135,20 +137,69 @@ const preloadImages = () => {
|
|
|
updateScale(); // 初始化缩放比例
|
|
|
|
|
|
const image = new Image();
|
|
|
- const totalFrames = SPRITE_CONFIG.totalFrames;
|
|
|
- let loadedFrames = 0;
|
|
|
|
|
|
- // 模拟加载进度
|
|
|
- const simulateLoading = () => {
|
|
|
- const interval = setInterval(() => {
|
|
|
- loadedFrames++;
|
|
|
- progress.value = (loadedFrames / totalFrames) * 100;
|
|
|
+ // 使用 fetch 流式下载并根据实际字节进度更新进度条
|
|
|
+ (async () => {
|
|
|
+ try {
|
|
|
+ // 释放之前的 object URL(如果有)
|
|
|
+ if (objectUrlRef) {
|
|
|
+ try {
|
|
|
+ URL.revokeObjectURL(objectUrlRef);
|
|
|
+ } catch (e) {}
|
|
|
+ objectUrlRef = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ const res = await fetch(Img);
|
|
|
+
|
|
|
+ // 优先使用可读流来计算进度
|
|
|
+ const contentLength = res.headers.get("content-length");
|
|
|
+ if (res.body && contentLength) {
|
|
|
+ const total = parseInt(contentLength, 10);
|
|
|
+ const reader = res.body.getReader();
|
|
|
+ const chunks = [];
|
|
|
+ let received = 0;
|
|
|
|
|
|
- if (loadedFrames >= totalFrames) {
|
|
|
- clearInterval(interval);
|
|
|
+ while (true) {
|
|
|
+ const { done, value } = await reader.read();
|
|
|
+ if (done) break;
|
|
|
+ chunks.push(value);
|
|
|
+ received += value.length;
|
|
|
+ progress.value = Math.min(100, (received / total) * 100);
|
|
|
+ }
|
|
|
+
|
|
|
+ const blob = new Blob(chunks);
|
|
|
+ objectUrlRef = URL.createObjectURL(blob);
|
|
|
+ spriteUrl.value = objectUrlRef;
|
|
|
+
|
|
|
+ image.onload = () => {
|
|
|
+ setTimeout(() => {
|
|
|
+ loaded.value = true;
|
|
|
+ resolve();
|
|
|
+ }, 300);
|
|
|
+ };
|
|
|
+
|
|
|
+ image.onerror = () => {
|
|
|
+ if (objectUrlRef) {
|
|
|
+ try {
|
|
|
+ URL.revokeObjectURL(objectUrlRef);
|
|
|
+ } catch (e) {}
|
|
|
+ objectUrlRef = null;
|
|
|
+ }
|
|
|
+ spriteUrl.value = Img;
|
|
|
+ image.src = Img;
|
|
|
+ resolve();
|
|
|
+ };
|
|
|
+
|
|
|
+ // 最后确保进度为 100%
|
|
|
+ progress.value = 100;
|
|
|
+ image.src = objectUrlRef;
|
|
|
+ } else {
|
|
|
+ // 无法使用流式读取(或没有 content-length),回退到直接拿 blob
|
|
|
+ const blob = await res.blob();
|
|
|
+ objectUrlRef = URL.createObjectURL(blob);
|
|
|
+ spriteUrl.value = objectUrlRef;
|
|
|
progress.value = 100;
|
|
|
|
|
|
- // 图片加载完成
|
|
|
image.onload = () => {
|
|
|
setTimeout(() => {
|
|
|
loaded.value = true;
|
|
|
@@ -156,12 +207,33 @@ const preloadImages = () => {
|
|
|
}, 300);
|
|
|
};
|
|
|
|
|
|
- image.src = Img;
|
|
|
- }
|
|
|
- }, 50);
|
|
|
- };
|
|
|
+ image.onerror = () => {
|
|
|
+ if (objectUrlRef) {
|
|
|
+ try {
|
|
|
+ URL.revokeObjectURL(objectUrlRef);
|
|
|
+ } catch (e) {}
|
|
|
+ objectUrlRef = null;
|
|
|
+ }
|
|
|
+ spriteUrl.value = Img;
|
|
|
+ image.src = Img;
|
|
|
+ resolve();
|
|
|
+ };
|
|
|
|
|
|
- simulateLoading();
|
|
|
+ image.src = objectUrlRef;
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ // 最终回退:直接使用导入路径
|
|
|
+ spriteUrl.value = Img;
|
|
|
+ progress.value = 100;
|
|
|
+ image.onload = () => {
|
|
|
+ setTimeout(() => {
|
|
|
+ loaded.value = true;
|
|
|
+ resolve();
|
|
|
+ }, 300);
|
|
|
+ };
|
|
|
+ image.src = Img;
|
|
|
+ }
|
|
|
+ })();
|
|
|
});
|
|
|
};
|
|
|
|
|
|
@@ -182,7 +254,7 @@ const spriteStyle = computed(() => {
|
|
|
return {
|
|
|
width: `${SPRITE_CONFIG.frameWidth * scale.value}px`,
|
|
|
height: `${SPRITE_CONFIG.frameHeight * scale.value}px`,
|
|
|
- backgroundImage: `url(${Img})`,
|
|
|
+ backgroundImage: `url(${spriteUrl.value})`,
|
|
|
backgroundPosition: getBackgroundPosition(),
|
|
|
backgroundSize: `${SPRITE_CONFIG.totalWidth * scale.value}px auto`,
|
|
|
backgroundRepeat: "no-repeat",
|
|
|
@@ -255,6 +327,15 @@ onBeforeUnmount(() => {
|
|
|
cancelAnimationFrame(animationFrame.value);
|
|
|
}
|
|
|
window.removeEventListener("resize", handleResize);
|
|
|
+ // 组件卸载时释放通过 createObjectURL 创建的临时 URL
|
|
|
+ if (objectUrlRef) {
|
|
|
+ try {
|
|
|
+ URL.revokeObjectURL(objectUrlRef);
|
|
|
+ } catch (e) {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+ objectUrlRef = null;
|
|
|
+ }
|
|
|
});
|
|
|
|
|
|
// 监听loaded状态变化
|