Pārlūkot izejas kodu

feat: realize picture progress bar through fetch

chenlei 3 nedēļas atpakaļ
vecāks
revīzija
afd44e2e0c

+ 97 - 16
packages/pc/src/components/Game7/index.vue

@@ -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状态变化

+ 91 - 20
packages/pc/src/components/Game8/index.vue

@@ -185,28 +185,78 @@ const updateScale = () => {
   scale.value = containerWidth / 1920;
 };
 
+const spriteUrl = ref(Img);
+let objectUrlRef = null;
+
 // 图片预加载
 const preloadImages = () => {
   return new Promise((resolve) => {
     updateScale(); // 初始化缩放比例
 
     const image = new Image();
-    const totalFrames = isFront.value
-      ? SPRITE_CONFIG.totalFrames
-      : SPRITE_CONFIG.totalFrames2;
-    let loadedFrames = 0;
-
-    // 模拟加载进度
-    const simulateLoading = () => {
-      const interval = setInterval(() => {
-        loadedFrames++;
-        progress.value = (loadedFrames / totalFrames) * 100;
-
-        if (loadedFrames >= totalFrames) {
-          clearInterval(interval);
+
+    // 使用 fetch 流式下载并根据实际字节进度更新进度条
+    (async () => {
+      try {
+        // 释放之前的 object URL(如果有)
+        if (objectUrlRef) {
+          try {
+            URL.revokeObjectURL(objectUrlRef);
+          } catch (e) {}
+          objectUrlRef = null;
+        }
+
+        const res = await fetch(isFront.value ? Img : Img2);
+
+        // 优先使用可读流来计算进度
+        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;
+
+          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;
@@ -214,12 +264,33 @@ const preloadImages = () => {
             }, 300);
           };
 
-          image.src = isFront.value ? Img : Img2;
-        }
-      }, 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;
+      }
+    })();
   });
 };
 
@@ -242,7 +313,7 @@ const spriteStyle = computed(() => {
   return {
     width: `${SPRITE_CONFIG.frameWidth * scale.value}px`,
     height: `${SPRITE_CONFIG.frameHeight * scale.value}px`,
-    backgroundImage: `url(${isFront.value ? Img : Img2})`,
+    backgroundImage: `url(${spriteUrl.value})`,
     backgroundPosition: getBackgroundPosition(),
     backgroundSize: `${isFront.value ? SPRITE_CONFIG.totalWidth : SPRITE_CONFIG.totalWidth2 * scale.value}px auto`,
     backgroundRepeat: "no-repeat",