iT邦幫忙

2026 iThome 鐵人賽

DAY 7
0
Software Development

從零打造網頁簡報編輯器系列 第 7

Day 07|把文字與圖片變成可操作的物件

  • 分享至 

  • xImage
  •  

系列:一張投影片背後的工程:從零打造網頁簡報編輯器

對應程式版本:Git tag day-07

版本說明:本文記錄當日的文字與圖片操作;後續圖形與群組功能仍共用相同的物件框與提交方式。

有了多張投影片後,下一個問題是讓內容不只是畫在畫布上。文字要能直接編輯,圖片要能從本機匯入,兩者都要能被選取、移動和刪除,操作結果還必須回到同一份文件資料。

共用位置資料,保留各自內容

文字與圖片都需要 id、位置與尺寸,因此兩種元件能使用同一套物件框和拖曳流程。各自特有的資料仍由可辨識聯集分開:文字保存內容和樣式,圖片保存資產 ID 與替代文字。

type SlideElement = TextElement | ImageElement

interface TextElement {
  type: 'text'
  text: string
  style: { fontSize: number; color: string; textAlign: TextAlign }
}

interface ImageElement {
  type: 'image'
  assetId: string
  alt: string
}

渲染時只要檢查 element.type,TypeScript 就能縮小型別,再交給 TextElementViewImageElementView。兩個元件收到相同的畫布尺寸、縮放比例、選取回呼與位置提交回呼,差異只留在真正不同的內容處理。

新增文字後直接進入編輯

按下「新增文字」會建立一個帶預設尺寸與樣式的物件,加入目前投影片,接著把它設為已選取和正在編輯。連續新增時稍微錯開位置,避免物件完全疊在一起。

文字內容使用受控的原生 textarea。平常設為唯讀,雙擊或按 Enter 才開始編輯;onChange 送出 element/set-text action。這樣瀏覽器原生的游標、選字和中文輸入法仍能工作,不必自行實作文字編輯器。

選取、編輯與文件內容分開保存。selectedElementIdeditingElementId 只屬於當下介面;真正需要重新開啟的文字則由 reducer 寫回文件。中文組字的按鍵保護會留到 Day 09 詳細處理。

圖片先通過檔案邊界

檔案選擇器用 accept="image/png,image/jpeg" 提示可用格式,但仍由 validateImageFile 實際檢查 MIME type 與 20 MB 上限。accept 不能取代驗證,因為外部檔案仍是不可信輸入。

通過後,程式使用瀏覽器的 createImageBitmap(file) 讀取圖片寬高,立即關閉 bitmap,再計算初始矩形。大圖會等比例縮進 800 × 500 範圍,小圖維持原尺寸,最後置中於 1600 × 900 畫布。

const fitScale = Math.min(
  1,
  800 / intrinsicSize.width,
  500 / intrinsicSize.height,
)

圖片 Blob 先寫入 IndexedDB,文件物件只保存 assetId。同一張圖片出現在編輯區、縮圖和列印頁面時,都由 AssetImage 讀取同一份資產;複製投影片也不會複製 Blob。若讀取或保存失敗,文件不會加入那個圖片物件,工具列會顯示錯誤。

文字與圖片共用一次拖曳流程

useElementDrag 同時服務兩種物件。按下主要指標時先選取物件並呼叫 setPointerCapture,後續移出物件邊界仍能收到事件。移動期間的位置只放在元件的暫時 state,畫面可以立即跟著游標更新;pointerup 才提交一次 element/move

const { position, pointerHandlers } = useElementDrag({
  position: { x: element.x, y: element.y },
  size: element,
  canvasSize,
  scale,
  onMoveCommit,
})

pointercancel 會丟棄暫時位置,不修改文件。文字正在編輯時則停用拖曳,避免使用者選字時移動整個文字框。縮放後的位移換算由共用座標函式處理,下一篇會專門驗證這條路徑。

測試物件的完整路徑

純函式測試確認 PNG/JPEG 與 20 MB 邊界,也驗證 2400 × 1200 圖片會縮成 800 × 400 並保持比例。Reducer 測試涵蓋文字新增、修改、移動和刪除,且原文件不被突變。

介面測試分別確認中文輸入,以及匯入一張模擬 PNG 後,編輯區、縮圖與列印區都取得圖片;接著讀回 IndexedDB,檢查文件中的圖片尺寸,並用資產 ID 取回對應的資產紀錄。day-07 當時的 6 個測試檔、24 個案例全部通過,Edge 也完成中文輸入、圖片保存和拖曳驗收。

下一篇會把顯示縮放、螢幕位移與邏輯座標串起來,確認同一次拖曳在不同比例下得到正確結果。

參考資料


上一篇
Day 06|新增切換與排序多張投影片
下一篇
Day 08|畫布縮放後座標如何保持正確
系列文
從零打造網頁簡報編輯器13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言