系列:一張投影片背後的工程:從零打造網頁簡報編輯器
對應程式版本:Git tag
v0.4.0版本說明:本文採用進階編排功能與位置鎖定都已具備的穩定版本。
單一物件的選取只要記住一個 ID;開始框選後,卻同時出現兩種狀態:哪些物件被選到,以及框選動作目前走到哪裡。若把它們都寫進簡報文件,重新整理後會留下不該保存的操作痕跡。
編輯器把 selectedElementIds 放在元件 state,真正的物件則從目前投影片推導:
const selectedElements = activeSlide.elements.filter(
(element) => selectedElementIds.includes(element.id),
)
這樣文件只保存投影片內容,選取、框選起點與預覽框都是暫時介面狀態。從投影片依 ID 推導出的 selectedElements 會保留物件順序,後面的群組、對齊與圖層操作都可重用同一份集合。
只有從空白畫布按下主要按鍵才開始框選。程式先用畫布目前比例把游標換回邏輯座標,再把起點與目前位置正規化成左、上、右、下四條邊。物件只要和框選矩形有面積重疊便命中:
const hitElements = activeSlide.elements.filter((element) =>
element.x < bounds.right &&
element.x + element.width > bounds.left &&
element.y < bounds.bottom &&
element.y + element.height > bounds.top,
)
這不是「整個物件必須包進框內」的規則。按住 Shift、Ctrl 或 Cmd 時,命中 ID 會和既有選取集合合併;沒有輔助鍵則先清空原選取。框選期間使用 Pointer Capture,所以游標離開畫布內容區仍能完整結束操作。

框選後,編輯器會同時選取兩個物件,並提供群組、對齊與圖層操作。
選取本身不保存,但 positionLocked 必須跟著物件存入文件:使用者重開簡報後,鎖定仍要有效。鎖定後,屬性面板欄位和控制點會停用;reducer 也會拒絕 move、translate、set-frame 與批次 frame 更新,避免只靠按鈕停用卻被快捷鍵繞過。
介面測試以 800 × 450 的顯示畫布框選兩個物件,確認顯示「已選取 2 個物件」;另一個測試鎖定文字框後按方向鍵,X 座標仍維持 180。這同時驗證框選和鎖定不是只有視覺效果。
下一篇會把多選的物件以同一個 groupId 關聯,讓一次拖曳成為一筆完整的群組移動。