修改 img.width/height 或 CSS 只改變顯示尺寸,FileReader 讀取的仍是原始檔案資料。要上傳縮放後的圖片,必須在 Canvas 繪製後提取 Blob/File。
input[type=file] 選取檔案FileReader.readAsDataURL → Image 載入canvas.width/height = 目標尺寸ctx.drawImage(img, 0, 0, 目標寬, 目標高)canvas.toBlob(callback, 'image/jpeg', 0.8) 獲取 BlobFormData 上傳
const input = document.getElementById('input_upload');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
input.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
// 1. 載入圖片
const img = await loadImage(file);
// 2. 計算縮放尺寸(例:最大 800px)
const MAX_DIM = 800;
let { width, height } = img;
if (width > MAX_DIM || height > MAX_DIM) {
const ratio = Math.min(MAX_DIM / width, MAX_DIM / height);
width *= ratio;
height *= ratio;
}
// 3. Canvas 繪製
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
// 4. 轉 Blob 並上傳
const blob = await blobFromCanvas(canvas, 'image/jpeg', 0.85);
await upload(blob, file.name);
});
function loadImage(file) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = URL.createObjectURL(file); // 比 DataURL 更高效
});
}
function blobFromCanvas(canvas, type, quality) {
return new Promise(resolve => canvas.toBlob(resolve, type, quality));
}
async function upload(blob, filename) {
const form = new FormData();
form.append('file', blob, filename);
const res = await fetch('/api/upload', { method: 'POST', body: form });
console.log('上傳結果', await res.json());
}
| 優化 | 說明 |
|---|---|
URL.createObjectURL | 避免 Base64 膨脹 33%,釋放時記得 URL.revokeObjectURL |
| EXIF 方向修正 | 手機拍照常含 Orientation 標籤,需用 exif-js 讀取並旋轉 Canvas |
| 分塊上傳 | 大檔案 (>10MB) 採用 spark-md5 計算哈希 + 分片並發上傳 |
| Web Worker | 將 Canvas 處理移至 Worker,不阻塞主執行緒 |
| 壓縮品質平衡 | 0.7~0.9 之間視業務調整,過低產生伪影 |
async function compressAll(files, options = {}) {
const { maxDim = 1920, quality = 0.85, type = 'image/jpeg', concurrency = 2 } = options;
const results = [];
const queue = [...files];
async function worker() {
while (queue.length) {
const file = queue.shift();
const img = await loadImage(file);
const { width, height } = calcDim(img, maxDim);
const canvas = document.createElement('canvas');
canvas.width = width; canvas.height = height;
canvas.getContext('2d').drawImage(img, 0, 0, width, height);
const blob = await blobFromCanvas(canvas, type, quality);
results.push({ original: file, compressed: blob, width, height });
}
}
await Promise.all(Array(concurrency).fill().map(worker));
return results;
}
掌握「載入 → 繪製 → 導出 Blob」三部曲,瀏覽器端即可實現專業級圖片預處理,大幅降低頻寬與伺服器壓力。