选择 Canvas 还是 SVG
Category(分类): HTML, JavaScript, 可视化 Status: 已整理
Canvas 和 SVG 都可以用于 Web 绘图、数据可视化和交互效果,但它们的绘图模型不同,适用场景也不同。
<canvas>是 HTML 提供的位图绘图表面,通常通过 JavaScript 使用 2D、WebGL 或其他绘图上下文;- SVG 是独立的 XML 矢量图形标准,也可以直接嵌入 HTML 文档;
- Canvas 更接近即时模式绘图,开发者负责每次绘制和更新像素;
- SVG 更接近保留模式绘图,每个图形通常对应一个可操作的 DOM 节点。
两者都不是“永远更快”或“只能用于某种图表”的技术。选择时应结合图形数量、更新频率、交互方式、可访问性、设备性能和开发成本判断。
一、Canvas 和 SVG 的基本区别
Canvas
Canvas 本身只是一个画布元素,绘图内容不会自动成为 DOM 节点:
<canvas id="canvas" width="600" height="300">
当前浏览器不支持 Canvas。
</canvas>
const canvas = document.querySelector('#canvas')
const context = canvas?.getContext('2d')
if (context) {
context.fillStyle = '#2563eb'
context.fillRect(20, 20, 120, 80)
}
Canvas 的特点:
- 适合像素级绘制和频繁重绘;
- 可以绘制大量点、线、图片和粒子;
- 不为每个图形创建 DOM 节点;
- 事件命中检测、焦点、键盘操作和可访问性需要开发者自行实现;
- 可以通过
toBlob()或toDataURL()导出图片; - 画布的实际像素尺寸会直接影响内存和绘制成本。
SVG
SVG 使用元素和属性描述图形:
<svg
width="600"
height="300"
viewBox="0 0 600 300"
role="img"
aria-label="示例矩形"
>
<rect x="20" y="20" width="120" height="80" fill="#2563eb" />
</svg>
SVG 的特点:
- 图形元素通常具有 DOM 节点;
- 可以使用 CSS、事件、动画和 DOM API;
- 适合需要单个图形交互、标签和可访问性的场景;
- 具备矢量缩放能力,适合图标、地图、流程图和中小规模图表;
- SVG 文档可以被序列化为 XML 文件;
- 元素数量过多、样式复杂或滤镜过多时,DOM、样式计算和绘制成本会增加。

二、适用场景
Canvas 常见适用场景
Canvas 通常适合:
- 热力图;
- 大量散点图;
- 粒子动画;
- 频繁变化的实时数据;
- 图片处理和像素级操作;
- 游戏画面和自定义绘图;
- 不需要为每个图形提供独立 DOM 交互的场景。
Canvas 的绘图能力更底层,开发者可以直接控制绘制顺序和像素,但也要自己处理:
- 坐标转换;
- 缩放和平移;
- 鼠标和触摸命中检测;
- 键盘焦点;
- 文本和图形的可访问性;
- 高 DPR 屏幕的画布适配。
SVG 常见适用场景
SVG 通常适合:
- 图标和 Logo;
- 流程图、组织结构图;
- 地图和区域选择;
- 中小规模折线图、柱状图和饼图;
- 需要单个节点悬停、点击和键盘交互的图表;
- 需要使用 CSS 控制样式和动画的图形;
- 需要无损缩放或导出矢量文件的场景。
SVG 的抽象层次更高,图形节点容易维护,也更容易接入浏览器的事件和样式系统。但当节点数量非常多时,DOM 和样式系统的成本可能成为瓶颈。
三、Canvas 和 SVG 的性能差异
网上经常出现“Canvas 性能更好,适合大量数据”的结论,但这个说法过于简单。性能取决于具体场景。
Canvas 主要受这些因素影响
- 画布 CSS 尺寸和实际像素尺寸;
devicePixelRatio;- 每帧绘制调用数量;
- 状态切换和路径数量;
- 图片纹理大小;
- 是否每帧重绘整个画布;
- 是否使用离屏 Canvas、WebGL 或分层缓存。
画布的实际像素尺寸可以这样计算:
const ratio = window.devicePixelRatio || 1
const rect = canvas.getBoundingClientRect()
canvas.width = Math.round(rect.width * ratio)
canvas.height = Math.round(rect.height * ratio)
const context = canvas.getContext('2d')
context?.scale(ratio, ratio)
DPR 越高,画布的实际像素数量通常越多,内存和绘制成本也可能增加。
SVG 主要受这些因素影响
- SVG 元素数量;
- DOM 层级和样式复杂度;
- 事件监听器数量;
- 文本、滤镜、阴影和渐变;
- 每次更新需要修改的节点数量;
- 是否频繁触发布局、样式计算和绘制。

没有适用于所有场景的性能排名
一般可以这样理解:
- 少量或中等数量图形、交互要求高:SVG 往往更容易实现和维护;
- 大量点、频繁刷新、图形不需要独立 DOM:Canvas 往往更合适;
- 超大规模数据或复杂粒子场景:可能需要 WebGL 或 WebGPU;
- 大画布不一定适合 SVG,节点数量和绘制复杂度同样重要;
- Canvas 也不一定适合超大画布,因为高分辨率位图可能消耗大量内存。
小数据量场景中,SVG 可能具有较低的实现和维护成本,缩放时也能保持矢量质量;但“SVG 内存更小、帧率更高”不是固定结论。应该在目标设备和真实数据规模下测试。
四、定制、交互和可访问性
SVG 的交互方式
SVG 图形通常对应 DOM 元素,因此可以直接使用:
click、pointerenter等事件;- CSS 类名和伪类;
:hover、:focus-visible;- CSS Transitions 和 Animations;
aria-label、role和键盘焦点;querySelector()、属性修改和 DOM 结构操作。
例如:
.bar {
fill: #2563eb;
transition: fill 0.2s ease;
}
.bar:hover,
.bar:focus-visible {
fill: #f97316;
outline: none;
}
<rect
class="bar"
tabindex="0"
role="img"
aria-label="一月销售额 120"
x="20"
y="40"
width="30"
height="120"
/>
Canvas 的交互方式
Canvas 内部的圆形、矩形和数据点不会自动成为 DOM 元素。要实现交互,通常需要:
- 监听 Canvas 的指针事件;
- 将页面坐标转换为画布坐标;
- 根据几何数据计算命中的图形;
- 绘制悬浮状态或提示框;
- 通过额外的 HTML 或 ARIA 内容提供可访问信息。
Canvas 也可以通过透明 HTML 层、辅助列表或数据表格补充键盘操作和屏幕阅读器支持。
因此,SVG 的交互入口通常更直接,而 Canvas 在大规模绘制和自定义算法方面自由度更高。
五、如何选择 Canvas、SVG 或 WebGL?
可以先回答下面几个问题:
1. 每个图形是否都需要独立交互?
- 是:优先考虑 SVG;
- 否:Canvas、WebGL 或 Canvas + HTML 提示层都可以考虑。
2. 图形数量和刷新频率如何?
- 少量图形、低频更新:SVG 通常足够;
- 大量图形、频繁更新:Canvas 可能更合适;
- 超大规模数据和复杂动画:评估 WebGL 或 WebGPU。
3. 是否需要无损缩放和矢量导出?
- 是:SVG 更自然;
- 只需要导出位图:Canvas 可以通过
toBlob()导出; - 两者都需要:可以同时提供 SVG 和 Canvas 导出方案。
4. 是否需要屏幕阅读器和键盘操作?
- SVG 可以较自然地建立语义节点;
- Canvas 需要额外提供 HTML、ARIA 或数据表格;
- 重要数据不能只存在于不可访问的像素中。
5. 页面是否需要混合渲染?
可以将 Canvas 用于大量数据点,将 SVG 或 HTML 用于坐标轴、标签、提示框和交互层。混合方案有时可以同时兼顾绘制性能和交互能力,但实现复杂度也会增加。
六、G2 使用不同的渲染方案
原文示例基于 G2 3.x。从 G2 3.2.7 开始,旧版 G2 支持在 Chart 级别选择 SVG 渲染,Canvas 仍然是默认渲染方式。相同页面可以创建不同 renderer 的图表,但这段 API 不应直接套用到所有新版本 G2。
G2 3.x 的 SVG 示例
示例需要准备容器和依赖:
<div id="mountNode"></div>
import G2 from '@antv/g2'
import DataSet from '@antv/data-set'
const data = [
{
name: 'London',
'Jan.': 18.9,
'Feb.': 28.8,
'Mar.': 39.3,
'Apr.': 81.4,
May: 47,
Jun: 20.3,
Jul: 24,
Aug: 35.6
},
{
name: 'Berlin',
'Jan.': 12.4,
'Feb.': 23.2,
'Mar.': 34.5,
'Apr.': 99.7,
May: 52.6,
Jun: 35.5,
Jul: 37.4,
Aug: 42.4
}
]
const dataSet = new DataSet()
const view = dataSet.createView().source(data)
view.transform({
type: 'fold',
fields: ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.'],
key: '月份',
value: '月均降雨量'
})
const chart = new G2.Chart({
container: 'mountNode',
forceFit: true,
height: 500,
padding: 'auto',
renderer: 'svg'
})
chart.source(view)
chart
.interval()
.position('月份*月均降雨量')
.color('name')
.adjust([
{
type: 'dodge',
marginRatio: 1 / 32
}
])
chart.render()

这段代码中的 G2 和 DataSet 必须通过依赖导入或对应版本的 CDN 脚本提供。forceFit、DataSet 和 renderer: 'svg' 都属于 G2 3.x API。
新版本 G2 的注意事项
G2 后续版本的 API 和渲染器配置发生过变化,可能使用不同的 autoFit、Renderer 对象或底层渲染引擎配置。升级 G2 时应以对应版本的官方文档为准,不要直接复制 3.x 示例。
同时,“所有图表都支持 Canvas 和 SVG”只能限定在对应版本和官方支持范围内,不能泛化到所有版本、插件和自定义图形。
七、Canvas 和 SVG 的混合方案
实际项目不一定要二选一。例如一个数据可视化页面可以采用:
- Canvas 绘制大量散点;
- SVG 绘制坐标轴和少量标记;
- HTML 绘制 Tooltip 和筛选控件;
- WebGL 绘制超大规模粒子;
- 数据表格提供无障碍和精确数据访问。
混合方案需要处理好:
- 坐标系转换;
- 事件层级;
- 滚动和缩放同步;
- 屏幕阅读器内容;
- Canvas 和 SVG 的分辨率差异;
- 多层绘制的性能。
八、常见误区
误区一:Canvas 一定比 SVG 快
错误。Canvas 可能减少 DOM 节点,但高分辨率大画布和频繁全量重绘也可能非常昂贵。
误区二:SVG 只适合少量图形
不完全正确。SVG 可以承载中等规模的图形,但当节点、样式和交互复杂到一定程度时,DOM 和绘制成本可能成为瓶颈。
误区三:Canvas 没有 DOM,所以不需要考虑可访问性
错误。没有 DOM 节点意味着需要额外设计可访问替代内容,而不是不需要考虑可访问性。
误区四:高 DPR 只会让画布更清晰,不影响性能
错误。提高 Canvas 实际像素尺寸可以改善清晰度,但也会增加内存、栅格化和绘制成本。
误区五:G2 的旧版示例可以直接用于当前版本
错误。G2 的 3.x、4.x 和后续版本 API 存在差异,使用前应确认项目依赖版本。
小结
- Canvas 和 SVG 各有适用场景,没有脱离上下文的性能排名;
- Canvas 适合像素级处理、频繁重绘和大量图形,但交互与可访问性需要额外实现;
- SVG 适合中小规模图形、独立节点交互、CSS 样式、矢量缩放和可访问性;
- 大画布会增加 Canvas 的像素和内存成本,SVG 节点过多也会增加 DOM 和绘制成本;
- 超大规模数据可以评估 WebGL、WebGPU、数据抽样和分层渲染;
- 性能比较必须在真实数据量、浏览器、设备和 DPR 下测试;
- G2
3.2.7的renderer: 'svg'属于旧版 API,升级 G2 时需要查阅对应版本文档; - Canvas、SVG、HTML 和 WebGL 可以组合使用,以平衡性能、交互和可访问性。