SVG 与 canvas 对比
SVG 和 Canvas 是 HTML5 中两种主要的图形技术,各有优势和适用场景。本节将从技术特性、性能、无障碍、适用场景等多个维度对比两者,帮助你在项目中做出正确的选择。
前置知识
阅读本节前,建议先了解:SVG 内联矢量图
基础概念
SVG(Scalable Vector Graphics)是一种基于 XML 的矢量图形格式,它描述图形的数学定义(路径、形状、颜色),浏览器根据定义实时渲染。Canvas 是一个位图绘图表面,通过 JavaScript 命令逐像素绘制。
两者的核心区别在于:SVG 是声明式的(描述"是什么"),Canvas 是命令式的(告诉"怎么画")。这决定了它们在性能、交互性和适用场景上的根本差异。
详细说明
核心对比表
| 对比维度 | SVG | Canvas |
|---|---|---|
| 图形类型 | 矢量图 | 位图 |
| 描述方式 | XML 声明式 | JavaScript 命令式 |
| 缩放 | 无损缩放 | 缩放后可能模糊 |
| DOM 操作 | 可操作每个元素 | 整个画布是单个元素 |
| 事件绑定 | 可绑定到每个图形 | 只能绑定到 canvas 元素 |
| 动画 | CSS 动画 + JS + SMIL | 仅 JavaScript |
| 无障碍 | DOM 可被屏幕阅读器识别 | 内容不可访问 |
| 适用场景 | 图标、图表、地图、交互图形 | 游戏、图像处理、大量动态元素 |
| 性能(少量元素) | 好 | 好 |
| 性能(大量元素) | 差(DOM 开销大) | 好(像素绘制快) |
| 文本搜索 | 可以被搜索引擎索引 | 不能 |
SVG 的优势与劣势
优势:
html
<!-- 1. 可缩放 -->
<svg width="50" height="50" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="#4285f4"/>
</svg>
<!-- 放大到任何尺寸都清晰 -->
<!-- 2. DOM 可访问 -->
<svg viewBox="0 0 100 100" role="img" aria-label="饼图:前端 60%,后端 40%">
<circle cx="50" cy="50" r="40" fill="#4285f4"/>
<path d="M50 50 L50 10 A40 40 0 0 1 84.64 70 Z" fill="#34a853"/>
</svg>
<!-- 屏幕阅读器可以读取 aria-label -->
<!-- 3. CSS 样式化 -->
<style>
.highlight { fill: #fbbc05; transition: fill 0.3s; }
.highlight:hover { fill: #f59e0b; }
</style>劣势:
- 大量元素时性能差(每个元素都是 DOM 节点)
- 不适合像素级操作(如图像滤镜)
- 复杂路径的性能不如 Canvas
Canvas 的优势与劣势
优势:
javascript
// 1. 高性能绘制大量元素
for (var i = 0; i < 10000; i++) {
ctx.fillStyle = 'rgba(' + Math.random()*255 + ',' +
Math.random()*255 + ',' + Math.random()*255 + ',0.5)';
ctx.fillRect(Math.random()*800, Math.random()*600, 4, 4);
}
// Canvas 绘制 10000 个矩形很快
// 2. 像素级操作
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// 可以逐像素读取和修改
// 3. 适合实时渲染(游戏)
function gameLoop() {
ctx.clearRect(0, 0, 800, 600);
// 绘制所有游戏对象
requestAnimationFrame(gameLoop);
}劣势:
- 内容不可被搜索引擎索引
- 屏幕阅读器无法识别绘制内容
- 缩放会模糊
- 没有自动的事件系统(需要手动实现碰撞检测等)
适用场景对比
| 场景 | 推荐 | 原因 |
|---|---|---|
| 图标/Logo | SVG | 矢量缩放、CSS 样式化 |
| 数据图表 | SVG 或 Canvas | 简单图表用 SVG,大数据量用 Canvas |
| 2D 游戏 | Canvas | 高性能实时渲染 |
| 地图 | SVG | 矢量缩放、交互式元素 |
| 图像处理 | Canvas | 像素级操作 |
| 动画 Banner | Canvas | 流畅动画 |
| 流程图 | SVG | DOM 可操作、可交互 |
| 粒子效果 | Canvas | 大量粒子渲染 |
| 可打印图形 | SVG | 矢量打印质量高 |
| 实时数据可视化 | Canvas | 高频率更新 |
实战示例
同一个图表的 SVG 和 Canvas 实现
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>SVG vs Canvas 图表</title>
<style>
body { font-family: sans-serif; display: flex; gap: 40px; padding: 20px; }
.chart { border: 1px solid #ddd; border-radius: 8px; }
</style>
</head>
<body>
<div>
<h2>SVG 饼图</h2>
<svg class="chart" width="250" height="250" viewBox="0 0 250 250"
role="img" aria-label="技术栈占比:HTML 30%,CSS 25%,JS 35%,其他 10%">
<!-- HTML: 30% = 108度 -->
<circle cx="125" cy="125" r="100" fill="#4285f4"
stroke="#fff" stroke-width="2"
stroke-dasharray="188.5 628.3" stroke-dashoffset="0"
transform="rotate(-90 125 125)"/>
<!-- CSS: 25% = 90度 -->
<circle cx="125" cy="125" r="100" fill="#34a853"
stroke="#fff" stroke-width="2"
stroke-dasharray="157.1 628.3" stroke-dashoffset="-188.5"
transform="rotate(-90 125 125)"/>
<!-- JS: 35% = 126度 -->
<circle cx="125" cy="125" r="100" fill="#fbbc05"
stroke="#fff" stroke-width="2"
stroke-dasharray="219.9 628.3" stroke-dashoffset="-345.6"
transform="rotate(-90 125 125)"/>
<!-- 其他: 10% = 36度 -->
<circle cx="125" cy="125" r="100" fill="#ea4335"
stroke="#fff" stroke-width="2"
stroke-dasharray="62.8 628.3" stroke-dashoffset="-565.5"
transform="rotate(-90 125 125)"/>
</svg>
</div>
<div>
<h2>Canvas 饼图</h2>
<canvas id="pieCanvas" class="chart" width="250" height="250"></canvas>
<script>
var canvas = document.getElementById('pieCanvas');
var ctx = canvas.getContext('2d');
var data = [
{ value: 30, color: '#4285f4', label: 'HTML' },
{ value: 25, color: '#34a853', label: 'CSS' },
{ value: 35, color: '#fbbc05', label: 'JS' },
{ value: 10, color: '#ea4335', label: '其他' },
];
var total = data.reduce(function(s, d) { return s + d.value; }, 0);
var startAngle = -Math.PI / 2;
var cx = 125, cy = 125, r = 100;
data.forEach(function(item) {
var sliceAngle = (item.value / total) * Math.PI * 2;
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, r, startAngle, startAngle + sliceAngle);
ctx.closePath();
ctx.fillStyle = item.color;
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 2;
ctx.stroke();
startAngle += sliceAngle;
});
</script>
</div>
</body>
</html>注意事项
- 不要在两者之间做绝对的选择——可以混合使用
- SVG 元素数量超过几千个时,考虑改用 Canvas
- 需要无障碍支持的场景优先选择 SVG
- 需要像素操作的场景只能用 Canvas
- 使用
aria-label或隐藏文本为 Canvas 提供无障碍描述
最佳实践
- 图标/Logo → SVG 内联或 SVG 文件
- 数据图表(少量数据)→ SVG(D3.js、Chart.js SVG 模式)
- 游戏/粒子效果 → Canvas
- 图像处理/滤镜 → Canvas
- 交互式流程图/组织架构图 → SVG
- 实时高频更新 → Canvas
下一节
继续学习:img 嵌入 SVG