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>
| 对比项 | Canvas | SVG |
|---|---|---|
| 渲染方式 | 位图(像素) | 矢量(数学描述) |
| 放大效果 | 会模糊、有锯齿 | 始终清晰 |
| DOM结构 | 单个canvas元素 | 每个图形都是DOM元素 |
| 事件处理 | 需要计算坐标 | 直接绑定到元素 |
| 性能 | 图形多时更好 | 图形少时更好 |
| 动画实现 | 需要手动重绘 | 支持CSS动画 |
| 文件体积 | 取决于分辨率 | 取决于复杂度 |
| 可访问性 | 较差 | 较好(支持屏幕阅读器) |
JavaScript代码 → 绑制命令 → 像素数据 → 显示
Canvas绑制完成后,只保存像素数据:
ctx.fillRect(0, 0, 100, 100) // 绑制矩形
ctx.clearRect(0, 0, 100, 100) // 清除矩形
// Canvas不知道这里曾经有个矩形
// 只知道每个像素的颜色
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更合适
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更合适 -->
<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的内存占用取决于图形复杂度,与显示尺寸无关。
<!-- SVG更适合 -->
<svg viewBox="0 0 24 24">
<path d="M12 2L2 22h20L12 2z"/>
</svg>
SVG图标放大不失真,可以用CSS修改颜色。
// Canvas更适合
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
// 更新游戏状态
updateGame()
// 绑制所有游戏元素
drawGame()
requestAnimationFrame(gameLoop)
}
游戏需要每帧重绘,Canvas更高效。
// 数据量大用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>