系列:一張投影片背後的工程:從零打造網頁簡報編輯器
對應程式版本:Git tag
v0.1.0版本說明:本文記錄第一版的單選屬性面板;後續多選功能會把相同的文件 action 擴展到多個物件。
拖曳適合大方向移動,但做簡報時常常需要精確數值:文字框距離左側 180 還是 200、字級是 44 還是 48。屬性面板的工作不是直接修改畫面,而是把使用者輸入轉成一筆合法的文件更新。
選取物件後,面板會顯示位置與大小;文字物件再多出字級、顏色與對齊。每個數字欄位都依目前畫布和物件狀態計算範圍:
| 欄位 | 最小值 | 最大值 |
|---|---|---|
| X | 0 | 畫布寬度 − 目前寬度 |
| Y | 0 | 畫布高度 − 目前高度 |
| 寬度 | 80 | 畫布寬度 − 目前 X |
| 高度 | 50 | 畫布高度 − 目前 Y |
| 字級(文字限定) | 8 | 200 |
這些範圍既放在 <input type="number"> 的 min、max 屬性,也會在提交前由程式重新檢查。瀏覽器的輸入提示改善操作體驗,程式檢查則保護文件資料不會出現 NaN、負數或超出畫布的矩形。
數字欄位使用 defaultValue,在 Enter 或失焦時才讀取內容:
onBlur={(event) => {
const next = Number(event.currentTarget.value)
if (Number.isFinite(next) && next >= min && next <= max) {
onCommit(Math.round(next))
} else {
event.currentTarget.value = String(Math.round(value))
}
}}
這避免使用者剛清空欄位、還沒輸入完整數字時,就把暫時的空值寫進文件。有效數字會四捨五入成邏輯座標;無效數字立即回復原值。Enter 只是呼叫 blur(),因此鍵盤和滑鼠離開欄位走同一條驗證流程。
顏色使用瀏覽器的色彩選擇器,對齊則是固定選項,兩者變更時可以直接提交。文字樣式只對文字物件顯示,圖片不會出現不適用的欄位。
位置或大小變更最後都送出同一種 action:
updateDocument({
type: 'element/set-frame',
slideId: resolvedActiveSlideId,
elementId: selectedElement.id,
frame: { ...position, ...size },
})
改 X 或 Y 時,程式使用 clampElementPosition;改寬或高時,則重用 Day 11 的 resizeElement。所以圖片從屬性面板改寬時仍會保持比例,文字框則可以自由改尺寸。文字字級、顏色與對齊使用 element/set-style;reducer 會沿著受影響的文件路徑建立新副本,包括被選取物件與它的 style,避免突變原文件。
介面測試先用控制點把文字框調成 620 × 200,再把「寬度」輸入為 0 並離開欄位,確認畫面仍維持 620 × 200。接著按復原,尺寸回到 520 × 150。Reducer 測試則確認同一個物件可依序更新框線與文字樣式,而原始文件不會被修改。
到這裡,拖曳與數字輸入都會匯入同一套文件更新規則。下一篇會從單選走向框選與多選,讓一套操作能同時套用到多個物件。