在前幾篇文章中,我們完成了《喵語日誌》的文字對話、情緒分析和溫暖 UI。
不過,在真實的生活紀錄裡,一張照片往往比一大段文字更能傳達當下的感受:可能是今天吃的精緻串燒、路上看到的夕陽,或是貓咪賣萌的瞬間。
今天,我們來替《喵語日誌》解鎖照片上傳功能。
手機隨手拍下的照片,通常都是高解析度大檔(動不動就 3MB~10MB)。如果直接將原始大圖轉成 Base64 傳送給 API,會遇到網路傳輸慢、Payload 過大導致 API 失敗,以及前端介面卡頓的問題。
因此,今天的目標是在前端完成:圖片格式與大小檢查、Canvas 等比例壓縮、即時預覽與拖曳上傳體驗。
為了在使用者選擇照片的瞬間自動完成「瘦身」,我們建立了一個 compressImage 工具函式。
其主要流程包含:
檢查檔案格式與大小 ➔ 用 FileReader 讀取 ➔ 用 Canvas 等比例縮放 ➔ 輸出壓縮後的 Base64 字串
src/utils/imageUtils.ts)// Canvas 繪製與壓縮核心邏輯
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
ctx?.drawImage(img, 0, 0, width, height);
// 輸出壓縮後的 Base64 字串 (JPEG 格式,品質 0.7)
const compressedBase64 = canvas.toDataURL("image/jpeg", quality);
我用一張實際照片測試:
壓縮後的圖片在聊天氣泡中依然清晰,但傳輸量大幅下降。
接下來,把圖片上傳功能整合到 ChatContainer.tsx,主要包含三個互動細節:
input (type=file),並透過相機 Icon 按鈕觸發點擊。dragover 與 drop 事件,並呼叫 preventDefault() 避免瀏覽器直接打開圖片分頁;拖曳覆蓋層加上 pointer-events-none 樣式,防止阻擋拖放事件。// 拖曳覆蓋層與預覽縮圖 UI 邏輯
{isDragging && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center bg-pink-50/80 border-2 border-dashed border-pink-300 rounded-3xl">
<p className="text-pink-600 font-medium">放開以放置照片 🐾</p>
</div>
)}
{/* 縮圖預覽與刪除按鈕 */}
<div className="relative mb-2 inline-block px-4">
<img className="h-16 w-16 rounded-xl object-cover border border-pink-200" alt="預覽" />
<button className="absolute -right-1 -top-2 rounded-full bg-stone-700 text-white p-1 text-xs">✕</button>
</div>
點擊相機圖示選取圖片後,輸入框上方會出現帶有移除按鈕的預覽縮圖。
直接將電腦中的照片拖曳進對話視窗時,畫面上會出現粉紅色虛線覆蓋層,提示「放開以放置照片 🐾」。
按下送出後,聊天氣泡成功渲染出壓縮後的照片與喵喵的回應。同時開啟 DevTools 控制台觀察壓縮數據:

壓縮後的圖片在聊天氣泡中依然清晰,但傳輸量大幅下降,對前端效能和使用者體驗都是很大的幫助。
這次實作讓我深刻體會到,優秀的 UI/UX 不僅在於看得見的動畫與配色,隱藏在背後的資料效能優化同樣關鍵。從格式與大小的防呆檢查、Canvas 等比例壓縮、點擊與拖曳上傳,到即時的預覽縮圖,在前端做好這些細節,不僅讓系統機能更穩健,也讓使用者上傳照片時體驗到零焦慮、無卡頓的流暢感。
現在的《喵語日誌》已經能同時處理文字與照片,讓使用者不只能跟喵喵說話,也能把生活中的美食或景致畫面直接分享給喵喵看。
明天我們將進入全新的資料視覺化單元!我們將引入 JavaScript 圖表庫 Chart.js,學習如何將使用者每天紀錄的心情數據,轉化為清晰美麗的週與月情緒折線圖,讓大家能一眼看懂自己這段時間的情緒起伏與轉變!喵~