Blob 與 Base64 的互轉實戰
在前端開發(如 Vue.js 或 React)處理圖片上傳或裁切時,經常需要在 Blob 與 Base64 之間轉換。本篇將提供完整的轉換程式碼,並點出常見的效能陷阱。
一、 情境 A:Blob 轉 Base64
用途
- 製作圖片預覽 (Preview)
- 將圖片存入
localStorage(因為 localStorage 只能存字串) - 透過 JSON API 傳輸小型圖片
程式碼
javascript
/**
* 將 Blob (或 File) 轉換為 Base64 字串
* @param {Blob} blob
* @returns {Promise<string>}
*/
function blobToBase64(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result); // result 就是 Base64
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
// 使用範例
async function handleFileSelect(file) {
const base64String = await blobToBase64(file);
console.log(base64String);
// 輸出: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
}現代替代方案
如果你的目標瀏覽器支援較新的 API,可以使用更簡潔的寫法:
javascript
async function blobToBase64Modern(blob) {
const buffer = await blob.arrayBuffer();
const bytes = new Uint8Array(buffer);
const binary = bytes.reduce(
(acc, byte) => acc + String.fromCharCode(byte),
""
);
return `data:${blob.type};base64,${btoa(binary)}`;
}二、 情境 B:Base64 轉 Blob
用途
- 圖片裁切後(Canvas 通常吐出 Base64),需要轉回 Blob 以便透過 API 上傳
- 接收 Base64 格式的圖片資料,需要建立 Blob URL 預覽
程式碼
javascript
/**
* 將 Base64 字串轉換為 Blob
* @param {string} base64 - 完整的 Data URL (含 data:image/png;base64, 前綴)
* @param {string} mimeType - 可選,預設從 Base64 中解析
* @returns {Blob}
*/
function base64ToBlob(base64, mimeType) {
// 1. 解析 MIME Type (如果沒有提供)
if (!mimeType) {
const match = base64.match(/^data:(.+?);base64,/);
mimeType = match ? match[1] : "application/octet-stream";
}
// 2. 去除開頭的 "data:image/png;base64," 並解碼
const byteString = atob(base64.split(",")[1]);
// 3. 建立 ArrayBuffer 並填入資料
const ab = new ArrayBuffer(byteString.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
// 4. 建立並回傳 Blob 物件
return new Blob([ab], { type: mimeType });
}
// 使用範例
const base64Image = "data:image/png;base64,iVBORw0KGgo...";
const blob = base64ToBlob(base64Image);
console.log(blob); // Blob { size: 1234, type: "image/png" }三、 效能陷阱與最佳實踐
陷阱 1:大圖片轉 Base64 會造成 UI 卡頓
FileReader.readAsDataURL() 會把整張圖片轉成一個超長字串。如果圖片有 5MB,Base64 字串會變成約 6.7MB,而瀏覽器需要:
- 讀取整個檔案到記憶體
- 編碼成 Base64 字串
- 把字串塞進 DOM(如果你設定給
<img src="...">)
這整個過程可能讓 UI 凍結數秒。
解法:使用 Blob URL 做預覽
javascript
// ❌ 不推薦:轉 Base64 做預覽
const base64 = await blobToBase64(largeFile); // 可能卡頓 2-3 秒
imageElement.src = base64;
// ✅ 推薦:使用 Blob URL
const url = URL.createObjectURL(largeFile); // 瞬間完成
imageElement.src = url;
// 記得在不需要時釋放
imageElement.onload = () => {
URL.revokeObjectURL(url);
};陷阱 2:忘記釋放 Blob URL
URL.createObjectURL() 會在瀏覽器記憶體中建立一個參照。如果你不主動呼叫 URL.revokeObjectURL(),這個參照會一直佔用記憶體直到頁面關閉。
> **記憶體洩漏**
如果你的應用頻繁建立預覽(例如圖片編輯器),忘記釋放 Blob URL 可能導致記憶體暴增。
javascript
// Vue 3 範例:在元件卸載時清理
import { ref, onUnmounted } from "vue";
const previewUrl = ref(null);
function setPreview(file) {
// 先釋放舊的
if (previewUrl.value) {
URL.revokeObjectURL(previewUrl.value);
}
previewUrl.value = URL.createObjectURL(file);
}
onUnmounted(() => {
if (previewUrl.value) {
URL.revokeObjectURL(previewUrl.value);
}
});四、 完整範例:圖片裁切上傳流程
以下是一個常見的實戰流程:使用者選擇圖片 → 裁切 → 上傳。
javascript
// 假設你使用 cropper.js 或類似的裁切庫
async function handleCropAndUpload(cropper) {
// 1. 取得裁切後的 Canvas
const canvas = cropper.getCroppedCanvas({
width: 800,
height: 600,
});
// 2. Canvas 轉 Blob (比 toDataURL 更有效率)
const blob = await new Promise((resolve) => {
canvas.toBlob(resolve, "image/jpeg", 0.9);
});
// 3. 建立 FormData 上傳
const formData = new FormData();
formData.append("avatar", blob, "cropped-image.jpg");
// 4. 發送請求
const response = await fetch("/api/upload", {
method: "POST",
body: formData,
});
return response.json();
}> **為什麼用 `canvas.toBlob()` 而不是 `canvas.toDataURL()`?**
toDataURL()回傳 Base64 字串,你還需要再轉成 Blob 才能上傳toBlob()直接回傳 Blob,省去轉換步驟,效能更好
五、 快速參考表
| 需求 | 方法 | 注意事項 |
|---|---|---|
| Blob → Base64 | FileReader.readAsDataURL() | 大檔案會卡 UI |
| Base64 → Blob | atob() + Uint8Array + new Blob() | 記得解析 MIME Type |
| 圖片預覽 | URL.createObjectURL() | 用完要 revokeObjectURL() |
| Canvas → 上傳用 Blob | canvas.toBlob() | 比 toDataURL() 效率高 |
總結
- Blob → Base64:使用
FileReader,但要注意大檔案的效能問題 - Base64 → Blob:使用
atob()解碼 +Uint8Array建構 - 圖片預覽:優先使用
URL.createObjectURL(),避免 Base64 的效能開銷 - Canvas 上傳:使用
canvas.toBlob()直接取得 Blob