Canvas 与 SVG 对比

Canvas和SVG都可以在网页上绑制图形,但它们的工作原理完全不同。选择哪种技术,取决于你的具体需求。

根本区别

Canvas:位图,用像素绑制,绑完就"忘"了。

SVG:矢量图,用数学描述图形,每个元素都是独立的DOM节点。

<!-- Canvas方式 -->
<canvas id="canvas" width="200" height="200"></canvas>
<script>
  const ctx = document.getElementById('canvas').getContext('2d')
  ctx.fillStyle = 'red'
  ctx.fillRect(50, 50, 100, 100)
</script>

<!-- SVG方式 -->
<svg width="200" height="200">
  <rect x="50" y="50" width="100" height="100" fill="red"/>
</svg>

详细对比

对比项CanvasSVG
渲染方式位图(像素)矢量(数学描述)
放大效果会模糊、有锯齿始终清晰
DOM结构单个canvas元素每个图形都是DOM元素
事件处理需要计算坐标直接绑定到元素
性能图形多时更好图形少时更好
动画实现需要手动重绘支持CSS动画
文件体积取决于分辨率取决于复杂度
可访问性较差较好(支持屏幕阅读器)

渲染原理差异

Canvas渲染过程

JavaScript代码 → 绑制命令 → 像素数据 → 显示

Canvas绑制完成后,只保存像素数据:

ctx.fillRect(0, 0, 100, 100)  // 绑制矩形
ctx.clearRect(0, 0, 100, 100) // 清除矩形

// Canvas不知道这里曾经有个矩形
// 只知道每个像素的颜色

SVG渲染过程

SVG标签 → DOM树 → 渲染树 → 显示

SVG每个图形都是DOM元素:

<svg>
  <rect id="myRect" x="0" y="0" width="100" height="100"/>
</svg>

<script>
  const rect = document.getElementById('myRect')
  rect.setAttribute('fill', 'red')  // 可以直接操作
  rect.addEventListener('click', () => {})  // 可以绑定事件
</script>

选择指南

使用Canvas的场景

  • 大量图形元素(上千个)
  • 实时渲染的游戏
  • 复杂的图像处理
  • 数据可视化(大量数据点)
  • 需要像素级操作
// 粒子系统,数千个粒子用Canvas更合适
const particles = []
for (let i = 0; i < 5000; i++) {
  particles.push({ x: Math.random() * 800, y: Math.random() * 600 })
}

function render() {
  ctx.clearRect(0, 0, 800, 600)
  particles.forEach(p => {
    ctx.fillRect(p.x, p.y, 2, 2)
  })
}

使用SVG的场景

  • 图形数量较少
  • 需要高清晰度(响应式设计)
  • 需要交互事件
  • 图标、Logo
  • 需要CSS动画
  • 需要可访问性
<!-- 交互式图表,SVG更合适 -->
<svg class="chart">
  <rect class="bar" x="10" y="10" width="30" height="100">
    <animate attributeName="height" from="0" to="100" dur="1s"/>
  </rect>
</svg>

<style>
.bar:hover { fill: orange; }
</style>

性能对比

图形数量对性能的影响

图形数量少(<100):
  SVG: ★★★★★  Canvas: ★★★★☆

图形数量中(100-1000):
  SVG: ★★★☆☆  Canvas: ★★★★☆

图形数量多(>1000):
  SVG: ★☆☆☆☆  Canvas: ★★★★★

内存占用

Canvas的内存占用主要取决于画布尺寸:

// 800x600的画布,RGBA四通道
// 内存占用 ≈ 800 × 600 × 4 = 1,920,000 字节 ≈ 1.8MB

SVG的内存占用取决于图形复杂度,与显示尺寸无关。

实际案例对比

案例1:简单图标

<!-- SVG更适合 -->
<svg viewBox="0 0 24 24">
  <path d="M12 2L2 22h20L12 2z"/>
</svg>

SVG图标放大不失真,可以用CSS修改颜色。

案例2:实时游戏

// Canvas更适合
function gameLoop() {
  ctx.clearRect(0, 0, canvas.width, canvas.height)
  
  // 更新游戏状态
  updateGame()
  
  // 绑制所有游戏元素
  drawGame()
  
  requestAnimationFrame(gameLoop)
}

游戏需要每帧重绘,Canvas更高效。

案例3:数据可视化

// 数据量大用Canvas
const dataPoints = 10000  // 一万个数据点

// Canvas绑制散点图
dataPoints.forEach(point => {
  ctx.fillRect(point.x, point.y, 2, 2)
})

// SVG绑制一万个元素会很卡

混合使用

有时候可以混合使用两种技术:

<!-- 背景用Canvas绑制复杂效果 -->
<canvas id="background"></canvas>

<!-- 前景用SVG绑制可交互元素 -->
<svg id="foreground">
  <circle cx="100" cy="100" r="50" class="interactive"/>
</svg>