iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
Software Development

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

Day 10|讓拖曳在不同縮放比例下都正確

  • 分享至 

  • xImage
  •  

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

對應程式版本:Git tag v0.1.0

版本說明:本文延續 Day 08 的座標換算,改從一次完整拖曳的事件生命週期說明實作。

Day 08 已經解決公式:螢幕位移要除以顯示比例,才能回到固定的邏輯畫布。真正做成拖曳功能後,還有另一個問題:游標會離開物件、操作可能被取消,而且不能在每一次 pointermove 都保存文件。

這一篇把拖曳拆成「記住起點、即時預覽、放開提交」三個階段,讓同一套行為在 50%、100% 或 125% 下都保持一致。

按下時只記錄一次起點

拖曳開始時,程式只接受主要按鍵,先選取物件,再保存這次操作專屬的資料:

dragRef.current = {
  pointerId: event.pointerId,
  screenStart: { x: event.clientX, y: event.clientY },
  elementStart: position,
}

elementStart 是文件中的原始邏輯座標,不會隨著每次畫面更新而改變。後續每一步都以它為基準,加上「目前游標減掉按下位置」的差值。這樣不會累積四捨五入誤差,也不會因為 React 畫面尚未更新而讀到舊位置。

同時呼叫 setPointerCapture(pointerId)。游標離開物件邊界後,後續指標事件仍會送回這個物件;不同手指或指標的事件則以 pointerId 排除,不會混進同一次拖曳。

移動只預覽,放開才寫入文件

pointermove 先用 Day 08 的函式把螢幕差值轉成邏輯差值,再加回起點、四捨五入並限制在畫布內:

const delta = screenDeltaToCanvas(
  event.clientX - drag.screenStart.x,
  event.clientY - drag.screenStart.y,
  scale,
)

const nextPosition = clampElementPosition(
  {
    x: Math.round(drag.elementStart.x + delta.x),
    y: Math.round(drag.elementStart.y + delta.y),
  },
  size,
  canvasSize,
)

nextPosition 只放在元件的暫時 state,畫面立刻跟著游標移動,文件資料卻保持不變。到了 pointerup,才以一次 element/move 提交最後座標;接著釋放 Pointer Capture 並清除預覽。這樣自動儲存與復原系統只看見一個完整動作。

若收到 pointercancel,暫時位置會直接丟棄,文件維持按下前的內容。文字框正在輸入時也傳入 disabled: true,避免選字或中文組字意外開啟拖曳。

用不同縮放比例驗證同一條路徑

介面測試的畫布比例是 50%。游標從 (100, 100) 移到 (150, 130),螢幕差值是 (50, 30),換算後應為邏輯差值 (100, 60);預設文字框便從 (180, 170)(280, 230)

座標純函式另外驗證 125%:螢幕移動 100 像素時,邏輯座標只前進 80 單位。兩個比例都通過,才能確認公式沒有只在預設縮放下碰巧正確。Edge 手動驗收也在不同縮放比例拖曳物件,游標與物件位移一致。

拖曳規則穩定後,下一篇會共用相同的事件生命週期,處理尺寸調整時為什麼圖片不能和文字框使用完全相同的規則。

參考資料


上一篇
Day 09|選取、文字焦點與中文輸入如何共存
下一篇
Day 11|調整大小時如何維持圖片比例
系列文
從零打造網頁簡報編輯器13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言