座標系統
SVG — 聲明式座標系,內建視窗變換
<!-- viewBox 定義世界座標,瀏覽器自動映射到元素尺寸 -->
<svg width="400" height="300" viewBox="0 0 1000 750">
<circle cx="500" cy="375" r="50" /> <!-- 世界座標 -->
</svg>
SVG 的 viewBox 就是一個內建的座標變換:
viewBox="0 0 1000 750" = 世界座標範圍
width="400" height="300" = 螢幕尺寸
- 瀏覽器自動計算縮放比:400/1000 = 0.4
Canvas — 命令式座標系,手動管理
// 沒有 viewBox,必須自己算
ctx.scale(scale, scale);
ctx.translate(offsetX, offsetY);
ctx.beginPath();
ctx.arc(500, 375, 50, 0, Math.PI * 2); // 世界座標
ctx.fill();
對比
|
SVG |
Canvas |
| 座標映射 |
viewBox 自動處理 |
手動 ctx.scale/translate |
| 重繪 |
瀏覽器自動(改 attribute 即可) |
必須清除 + 完整重繪 |
| 表達方式 |
聲明式(DOM 節點) |
命令式(繪圖指令) |
縮放方式
SVG 有三種縮放方法
<!-- 方法 1:改 viewBox(最自然,類似 Canvas 的世界座標變換) -->
<svg viewBox="250 187 500 375"> <!-- 放大 2 倍,看中心區域 -->
<!-- 方法 2:用 <g> 的 transform(類似 CSS transform) -->
<svg>
<g transform="scale(2) translate(-125, -93)">
<circle cx="500" cy="375" r="50" />
</g>
</svg>
<!-- 方法 3:CSS transform(和 img 一樣,拉伸像素) -->
<svg style="transform: scale(2);">
| 方法 |
畫質 |
重排版 |
適用場景 |
| 改 viewBox |
清晰(向量重算) |
是 |
地圖、資料視覺化 |
<g> transform |
清晰(SVG 內部變換) |
部分 |
局部放大 |
| CSS transform |
可能模糊(光柵化後縮放) |
否 |
簡單動畫 |
Canvas 只有一種方法
ctx.clearRect(0, 0, w, h);
ctx.save();
ctx.scale(scale, scale);
ctx.translate(offsetX, offsetY);
drawEverything(ctx); // 必須重繪
ctx.restore();
關鍵差異:SVG 改 viewBox 後,瀏覽器用向量重新算每個圖形,永遠清晰。Canvas 也是重繪所以也清晰。但 CSS transform 作用在 SVG 上時,瀏覽器可能先光柵化再縮放,就會模糊。
點擊偵測
SVG — 瀏覽器自動處理
<!-- SVG 元素就是 DOM 節點,事件直接綁 -->
<circle cx="500" cy="375" r="50" (click)="onClick($event)" />
即使套了 viewBox 或 <g> transform,瀏覽器自動換算命中區域。
Canvas — 必須手動反算
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const screenX = e.clientX - rect.left;
const screenY = e.clientY - rect.top;
// 手動反算世界座標
const worldX = (screenX - offsetX) / scale;
const worldY = (screenY - offsetY) / scale;
// 再逐一判斷是否命中某個圖形
shapes.forEach(shape => {
if (isInsideShape(worldX, worldY, shape)) { /* ... */ }
});
});
|
SVG |
Canvas |
| 點擊偵測 |
瀏覽器自動,支援事件冒泡 |
完全手動,自己算座標 + 命中判定 |
| 座標轉換 |
自動處理 viewBox/transform |
手動 (screen - offset) / scale |
| hover 效果 |
CSS :hover 直接用 |
自己追蹤滑鼠位置 + 重繪 |
搭配 touch-action
|
SVG |
Canvas |
HTML 可捲動內容 |
| 最佳縮放方式 |
改 viewBox |
ctx.scale + 重繪 |
transform + margin |
| 平移方式 |
改 viewBox 的 x, y |
ctx.translate + 重繪 |
原生捲動 |
| touch-action |
none(JS 全權處理) |
none(JS 全權處理) |
pan-x pan-y(捲動交給瀏覽器) |
| HammerJS 負責 |
pinch + pan |
pinch + pan |
只負責 pinch |
SVG 和 Canvas 都用 touch-action: none,因為平移都是 JS 自己算的(改 viewBox 或 ctx.translate),不需要瀏覽器原生捲動。
選擇建議
| 場景 |
選擇 |
原因 |
| 圖形數量少、需要互動 |
SVG |
事件處理免費,向量永遠清晰 |
| 圖形數量多(>1000) |
Canvas |
DOM 節點太多 SVG 會卡 |
| 靜態圖表 + 縮放 |
SVG |
viewBox 最簡單 |
| 即時繪圖/遊戲 |
Canvas |
命令式重繪更可控 |
| 長文件/報表 |
HTML + scroll 策略 |
需要原生捲動能力 |
| 單張圖片 |
HTML + transform 策略 |
最簡單,GPU 加速 |