無限畫布用 CSS transform 縮放整個圖層

物件只存世界座標,由外層容器套 transform: translate() scale() 一次縮放整個圖層,讓瀏覽器用 GPU 合成。

做法

<div
  className="ic-world"
  style={{ transform: `translate(${offsetX}px, ${offsetY}px) scale(${scale})` }}
>
  <rect x={100} y={100} width={200} height={200} />
  {/* 所有物件用世界座標畫 */}
</div>

矩形寫在 SVG 裡的就是世界座標數字,從頭到尾沒動過。要縮放時改外層 transform,瀏覽器把 .ic-world 當一張圖整張等比縮放,裡面所有 children 一起被縮。

視覺比喻

.ic-world 想成一張透明貼紙,物件貼在貼紙上的世界座標位置不動,是整張貼紙縮小,所以物件看起來也跟著變小。

為什麼選這套架構

  • 資料乾淨:物件只存世界座標,從不為了顯示重算
  • GPU 加速:改 transform 不會觸發 re-layout,只重新合成圖層,60fps 沒問題
  • 可擴充:新增物件類型只要會畫世界座標,不必懂縮放邏輯

幾乎所有無限畫布(Figma、Miro、tldraw、Excalidraw)都用這套。

另一條(不推薦的)路

讓每個物件自己算:

<rect x={100 * scale + offsetX} y={...} width={200 * scale} />

缺點:每次平移/縮放所有物件 props 都變,React 全部 re-render,失去 GPU 合成優勢,到處要記得算座標。

副作用

CSS scale 連邊框、文字、控制點都會一起縮,要保持「螢幕上看起來固定大小」的元素必須反向除以 scale → 見 CSS transform 縮放下 strokeWidth 要除以 scale

相關