發布日期: 2026年6月29日
難度: 中級
所需時間: 約 30-40 分鐘
前言
在現代社群媒體與網頁設計中,將多張照片組合成一張精美拼貼(Collage)的需求非常高。市面上有許多拼貼工具,但多數需要下載 App 或是含有滿滿的廣告浮水印。
本篇文章將帶領大家深入前端開發,使用 HTML5 Canvas、CSS Grid 以及 Vanilla JavaScript,實作一個兼具「毛玻璃質感 (Glassmorphism)」與「高互動性」的線上照片拼貼工具!
我們將挑戰並實作以下三個核心互動技術: 1. 多網格版型切換(採用彈性的 CSS Grid 進行版面切割)。 2. 圖片互動裁剪與縮放(滑鼠拖曳移動位置、滑桿控制圖片縮放)。 3. 高品質 Canvas 畫布輸出(後台以 3 倍高解析度繪製整張網格與圖片)。
技術架構與挑戰
在開發照片拼貼工具時,有兩個主要的技術挑戰:
- 挑戰一:如何限制圖片的顯示範圍?
在 CSS 中,我們可以簡單地使用 overflow: hidden 讓溢出的圖片隱藏,並透過 transform 讓使用者拖曳調整。然而,在將這些設定繪製到 Canvas 時,必須經過精準的比例計算,才能把網頁上「看到的」裁剪範圍原封不動地「畫出來」。
- 挑戰二:如何保證下載圖片的清晰度?
如果直接按照網頁上顯示的寬度(例如 500px)去繪製 Canvas,下載下來的圖片在手機上看起來會非常模糊。因此,我們必須在繪製時,將所有的座標和尺寸放大 3 倍進行渲染,以獲得極為銳利的高品質 PNG 圖檔。
第一步:建構網格版型 (HTML5 + CSS Grid)
我們定義一個主容器 .collage-wrapper,並透過切換 CSS 類名(如 .layout-1x2、.layout-2x2 等)來動態套用不同的 CSS Grid 屬性。
<div class="collage-wrapper layout-1x2" id="collage-wrapper">
<!-- 動態生成多個拼貼網格 (Cells) -->
</div>
我們使用 CSS Grid 來做簡潔的面版配置。例如,常見的 四格切分 (2×2) 與 左大右小三格 (asymmetric):
/* 四格切分 layout-2x2 */
.layout-2x2 {
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
}
/* 左大右小 layout-asym-1 */
.layout-asym-1 {
grid-template-columns: 2fr 1.2fr;
grid-template-rows: 1fr 1fr;
}
/* 左側第一格跨越兩行 */
.layout-asym-1 .cell-0 {
grid-row: span 2;
}
這比起傳統使用 absolute 定位或 table 佈局,CSS Grid 能在極少代碼下完美適應圓角與邊框。
第二步:實作圖片滑鼠拖曳與縮放 (JS)
我們在每個網格(Cell)內部放置一個絕對定位的 元素。我們建立一個物件 cellStates 來儲存每格照片的狀態:
cellStates[index] = {
file: fileObject, // 上傳的圖片檔案
imgElement: imgTag, // 顯示在網頁上的 <img> 標籤
scale: 1.0, // 縮放倍率
x: 0, // X 軸平移偏移量
y: 0 // Y 軸平移偏移量
};
2-1. 滑鼠拖曳位置調整
當使用者在網格內按住滑鼠時,我們會記錄初始滑鼠座標,並在滑鼠移動時計算差值,動態更新該格圖片的 transform 樣式:
cell.addEventListener('mousedown', function(e) {
if (!cellStates[index]) return;
isDragging = true;
startX = e.clientX - cellStates[index].x;
startY = e.clientY - cellStates[index].y;
});
window.addEventListener('mousemove', function(e) {
if (!isDragging) return;
cellStates[index].x = e.clientX - startX;
cellStates[index].y = e.clientY - startY;
// 更新網頁上的圖片位置
state.imgElement.style.transform = `translate(${state.x}px, ${state.y}px) scale(${state.scale})`;
});
這項技術能讓使用者直覺地透過拖曳,把相片最精彩的區域(例如人臉)調整到網格中心。
第三步:高品質 Canvas 後台渲染
當使用者點擊「匯出拼貼」按鈕時,是本專案最核心的部分。我們建立一個虛擬的 ,將畫布大小擴大 3 倍,並逐格繪製圖片。
3-1. 處理圓角裁剪 (Clip Rounded Corners)
如果使用者設定了網格圓角,我們必須在繪製圖片前,在 Canvas 上畫出該網格的圓角路徑,並呼叫 ctx.clip() 來做邊界剪裁:
exportCtx.save();
if (borderRadiusVal > 0) {
exportCtx.beginPath();
exportCtx.moveTo(cellX + borderRadiusVal, cellY);
exportCtx.arcTo(cellX + cellW, cellY, cellX + cellW, cellY + cellH, borderRadiusVal);
exportCtx.arcTo(cellX + cellW, cellY + cellH, cellX, cellY + cellH, borderRadiusVal);
exportCtx.arcTo(cellX, cellY + cellH, cellX, cellY, borderRadiusVal);
exportCtx.arcTo(cellX, cellY, cellX + cellW, cellY, borderRadiusVal);
exportCtx.closePath();
exportCtx.clip(); // 限制後續繪圖只能在此圓角內顯示
}
3-2. 實作 Cover 縮放比例計算 (維持長寬比)
因為每格網格的寬高比不同,直接拉伸圖片會造成變形。我們必須在 Canvas 繪製時重新計算圖片大小,使其維持比例地填滿網格(如同 CSS 的 object-fit: cover):
// 計算圖片與網格的比例關係
const imgAspect = tempImg.width / tempImg.height;
const cellAspect = cellW / cellH;
let drawW, drawH;
if (imgAspect > cellAspect) {
drawH = cellH;
drawW = cellH * imgAspect;
} else {
drawW = cellW;
drawH = cellW / imgAspect;
}
// 加入使用者的自訂平移與縮放設定
const finalW = drawW * state.scale;
const finalH = drawH * state.scale;
const finalX = cellX + (cellW - finalW) / 2 + (state.x * exportScale);
const finalY = cellY + (cellH - finalH) / 2 + (state.y * exportScale);
// 繪製至 Canvas
exportCtx.drawImage(tempImg, finalX, finalY, finalW, finalH);
exportCtx.restore(); // 恢復畫布狀態,準備繪製下一格
當所有網格都載入並繪製完畢後,執行 canvas.toDataURL('image/png') 來導出並下載最終的完美拼貼!
第四步:部署
我們將此專案打包為單一 HTML 檔案,並在 Nginx 伺服器網頁根目錄中建立 /collage/ 目錄:
mkdir -p /var/www/wordpress/collage
cp index.html /var/www/wordpress/collage/index.html
chown -R www-data:www-data /var/www/wordpress/collage
透過 Nginx 直配靜態檔案的特性,網頁載入速度達到了極限,而且不需要佔用伺服器的 PHP/MySQL 資源。
結語
本專案結合了 CSS Grid 版面佈局 與 HTML5 Canvas 圖像繪製 兩大實用技術。自己實作線上拼貼工具不僅可以完全掌控代碼、保障隱私,更沒有任何討人厭的廣告與付費門檻限制。
*歡迎點擊我們的線上拼貼工具親自體驗:(https://stillbuilding.life/collage/)*



