preloader
心得

Web Gesture: Canvas Element Zoom-Pan Strategy | Canvas 元素的縮放策略

Web Gesture: Canvas Element Zoom-Pan Strategy | Canvas 元素的縮放策略

Canvas 元素的縮放策略

Canvas 比較特殊,Transform 和 Scroll 策略都不是最佳選擇。最適合的是 Canvas 原生重繪

三種方案比較

1. Transform 策略(可用,但有缺點)

transform: scale3d(2, 2, 1);
  • 放大的是 canvas 的像素點(bitmap),畫面會模糊
  • 就像把一張 JPG 放大一樣,因為 canvas 內容是光柵化(rasterized)的
  • 優點:實現最簡單,適合不在意畫質的場景

2. Scroll 策略(不適合)

  • Canvas 是單一元素,沒有子元素可以獨立縮放
  • Canvas 內容不會溢出產生 scrollbar,margin 技巧無意義

3. Canvas 原生重繪(最佳)

// 不改 CSS,改 canvas 內部的繪圖座標系
ctx.save();
ctx.scale(scale, scale);
ctx.translate(offsetX, offsetY);
// 重新繪製所有內容
drawEverything(ctx);
ctx.restore();
  • 每次縮放/平移時,用新的座標系重新繪製
  • 畫面永遠清晰,因為是用放大後的解析度重新畫的
  • 這是 Google Maps、Figma、Excalidraw 等工具的做法

為什麼 Canvas 原生重繪最好

Transform Canvas 重繪
放大後畫質 模糊(拉伸像素) 清晰(重新繪製)
平移 translate3d ctx.translate
效能 GPU 處理,但浪費(模糊的圖放大) 需重繪,但可優化(只畫可見區域)
互動偵測 需反算座標 需反算座標(兩者都要)

關鍵知識點

1. Canvas 座標轉換

// 螢幕座標 → Canvas 世界座標(用於點擊偵測)
function screenToWorld(screenX: number, screenY: number) {
  return {
    x: (screenX - offsetX) / scale,
    y: (screenY - offsetY) / scale,
  };
}

2. 以滑鼠/手指位置為中心縮放

// pinch 中心點不動,周圍內容縮放
function zoomAt(newScale: number, centerX: number, centerY: number) {
  offsetX = centerX - (centerX - offsetX) * (newScale / scale);
  offsetY = centerY - (centerY - offsetY) * (newScale / scale);
  scale = newScale;
  redraw();
}

這是地圖類應用的標準做法:pinch 的中心點保持不動。

3. 效能優化

  • Dirty region:只重繪變化的區域
  • 分層 canvas:靜態背景一層、動態內容一層
  • OffscreenCanvas:在 Web Worker 中繪製,不阻塞主執行緒

學習資源

主題 資源
Canvas 座標變換 MDN: Transformations
Canvas 效能 MDN: Optimizing canvas
實作範例 Canvas zoom and pan (GitHub)
進階:Figma 架構 Figma’s Engineering Blog

簡單結論

  • 不在意畫質 → Transform 策略最快能跑起來
  • 需要清晰縮放 → Canvas 自己的 ctx.scale() + ctx.translate() 重繪