跳至主要內容
Skip to content

檔案上傳系統設計 (7) —— 客戶端預處理的威力

在上一篇中,我們提升了用戶的上傳體驗。但如果用戶上傳的是原始的 4K 照片(可能 10MB 以上),而我們只需要一個用於顯示的縮圖,直接傳送到伺服器會造成巨大的頻寬浪費。

本篇將介紹如何利用瀏覽器的運算能力,在檔案離開用戶電腦前先進行「預處理」。


一、 圖片壓縮:省下 90% 的頻寬

我們可以使用 HTML5 的 Canvas API 在前端直接縮小圖片尺寸並降低品質。

實作思路:

  1. 使用 URL.createObjectURL 讀取檔案。
  2. 將圖片繪製到畫布上。
  3. 透過 canvas.toBlob()canvas.toDataURL() 導出壓縮後的版本。
javascript
async function compressImage(file, { maxWidth = 800, quality = 0.7 }) {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onload = (e) => {
      const img = new Image();
      img.onload = () => {
        const canvas = document.createElement("canvas");
        let width = img.width;
        let height = img.height;

        // 計算比例縮放
        if (width > maxWidth) {
          height = (maxWidth / width) * height;
          width = maxWidth;
        }

        canvas.width = width;
        canvas.height = height;
        const ctx = canvas.getContext("2d");
        ctx.drawImage(img, 0, 0, width, height);

        canvas.toBlob((blob) => resolve(blob), "image/jpeg", quality);
      };
      img.src = e.target.result;
    };
    reader.readAsDataURL(file);
  });
}

二、 解決幻之旋轉:EXIF 資訊處理

你有遇過上傳的手機照片「自動躺平」嗎?這是因為照片中含有 EXIF (Exchangeable image file format) 中的 Orientation 標籤,而以前的瀏覽器在 Canvas 繪圖時常會忽略它。

解決方案:

現代瀏覽器在 drawImage 時大多已能自動處理,但若遇到舊環境,可以使用 browser-image-compressionblueimp-load-image 等函式庫來確保圖片方向正確。


三、 秒傳的前哨站:前端哈希 (Hash) 計算

在「效能優化篇」我們提過「秒傳」。為了實現秒傳,前端必須先計算檔案的 Hash 值傳給後端比對。

對於大檔案,直接計算會讓 JS 主執行緒卡死。推薦使用 Web Workers 來進行非同步計算。

javascript
// 在 Worker 中計算
import SparkMD5 from "spark-md5";

function calculateHash(file) {
  return new Promise((resolve) => {
    const spark = new SparkMD5.ArrayBuffer();
    const reader = new FileReader();
    const blobSlice =
      File.prototype.slice ||
      File.prototype.mozSlice ||
      File.prototype.webkitSlice;
    const chunkSize = 2097152; // 2MB
    let currentChunk = 0;
    const chunks = Math.ceil(file.size / chunkSize);

    reader.onload = (e) => {
      spark.append(e.target.result);
      currentChunk++;
      if (currentChunk < chunks) {
        loadNext();
      } else {
        resolve(spark.end());
      }
    };

    function loadNext() {
      const start = currentChunk * chunkSize;
      const end =
        start + chunkSize >= file.size ? file.size : start + chunkSize;
      reader.readAsArrayBuffer(blobSlice.call(file, start, end));
    }

    loadNext();
  });
}

總結

客戶端預處理的優點顯而易見:

  1. 節省流量:大幅減少伺服器頻寬支出。
  2. 減輕伺服器壓力:複雜的壓縮運算分散給每個用戶。
  3. 極致速度:結合 Hash 計算,達成「瞬間上傳」的神奇體驗。

下一篇,我們將挑戰最複雜的前端邏輯:分片上傳的前端排程。


️ 進階挑戰

如果在前端進行了圖片壓縮,原本檔案中的 Metadata(如拍照地點、快門時間)會消失。這對注重隱私或注重攝影數據的應用來說,分別會有什麼影響?


延伸閱讀與資源

← 上一章:前端上傳的專業修養 | 返回專案首頁 | 下一章:分片上傳的前端排程