打开一个数据大屏,满屏是跳动的折线图;打开一个在线白板,你可以随手涂鸦;打开一个网页小游戏,角色在屏幕上跑来跑去……这些画面背后,往往是前端里的两支"画笔":SVG 和 Canvas。
很多刚接触前端的朋友会疑惑:它们都能画图,到底有什么区别?什么时候该用哪个?
一、先打个比方:剪纸贴画 vs 黑板粉笔
想象你要在墙上布置一幅画,有两种做法:
做法一:剪纸贴画。 你剪出一个圆形、一个三角形、一段彩带,一张张贴到墙上。每一张纸片都是独立的。想把圆挪个位置?揭下来重新贴就行。想把三角形换成红色?直接换一张。别人问"这块是什么",你还能指着说"这是太阳"。
做法二:在黑板上用粉笔画。 你拿起粉笔,唰唰几笔画出圆和三角形。画完以后,黑板上只有一片粉笔灰,黑板并不知道哪里是圆、哪里是三角形。想挪动那个圆?对不起,只能擦掉重画。
这两种做法,基本就对应了:
- SVG ≈ 剪纸贴画:每个图形都是独立存在、可以单独操作的"对象"。
- Canvas ≈ 黑板粉笔:画上去的只是像素,画完就"忘了"自己画过什么。
记住这个比喻,后面的所有区别几乎都能从这里推出来。
二、SVG:用"文字描述"画出来的图
1. 它是什么
SVG 的全称是 Scalable Vector Graphics,可缩放矢量图形。名字里有两个关键词:
- 矢量(Vector):图形不是由一个个像素点拼起来的,而是用数学方式描述的。比如"圆心在 (50, 50),半径 40"。
- 可缩放(Scalable):既然是数学描述,那放大十倍也只是把数字乘以十,依旧清晰锐利,不会出现马赛克。
2. 长什么样
SVG 本质上是一段 XML 格式的文字,和 HTML 长得很像:
<svg width="200" height="200">
<circle cx="100" cy="100" r="80" fill="orange" />
<rect x="70" y="70" width="60" height="60" fill="skyblue" />
<text x="100" y="190" text-anchor="middle">你好 SVG</text>
</svg>
这段代码的意思就像在"口述"一幅画:在中间画一个橙色的圆,上面放一个天蓝色的方块,底下写一行字。浏览器读到这段描述,就把图画出来。
3. 它的特点
(1)每个图形都是网页元素
<circle>、<rect> 和 <div> 一样,都是 DOM 树里的节点。所以你可以:
const circle = document.querySelector('circle');
circle.addEventListener('click', () => {
circle.setAttribute('fill', 'red');
});
点击圆,它就变红。不需要任何计算,浏览器自动帮你判断"鼠标点中的是不是这个圆"。
(2)可以用 CSS 来装扮
circle:hover {
fill: tomato;
transition: fill 0.3s;
}
鼠标悬停变色、加动画,都和写普通网页一样简单。
(3)无限清晰
在普通屏幕、高清屏、甚至放大到海报尺寸,SVG 都不会糊。这也是为什么网站的 Logo 和图标大量使用 SVG。
(4)对搜索引擎和读屏软件友好
SVG 里的文字是真正的文字,可以被选中、被搜索、被读屏软件朗读,对视障用户更友好。
三、Canvas:一块可以随意挥洒的"像素画布"
1. 它是什么
Canvas 是 HTML5 带来的一个标签 <canvas>。它本身只是一块空白的矩形区域,就像一块空黑板。真正的绘画工作要靠 JavaScript 来完成。
2. 长什么样
<canvas id="board" width="200" height="200"></canvas>
<script>
const canvas = document.getElementById('board');
const ctx = canvas.getContext('2d'); // 拿到"画笔"
ctx.fillStyle = 'orange';
ctx.beginPath();
ctx.arc(100, 100, 80, 0, Math.PI * 2);
ctx.fill(); // 画一个圆
ctx.fillStyle = 'skyblue';
ctx.fillRect(70, 70, 60, 60); // 画一个方块
ctx.fillStyle = 'black';
ctx.textAlign = 'center';
ctx.fillText('你好 Canvas', 100, 190); // 写字
</script>
画出来的效果和上面的 SVG 几乎一样,但过程完全不同:这里是一条条命令,"拿橙色笔、画个圆、涂满;换蓝色笔、画个方块……"
3. 它的特点
(1)画完就只剩像素
执行完上面的代码,Canvas 里并没有"圆"和"方块"这两个东西,只有 200×200 个彩色像素点。你用开发者工具查看,只能看到一个孤零零的 <canvas> 标签。
(2)想交互,得自己动手
如果想实现"点击圆就变红",Canvas 可没法帮你。你得自己做这些事:
- 监听整个画布的点击事件;
- 拿到鼠标坐标;
- 自己算一算这个点在不在圆里(点到圆心距离是否小于半径);
- 如果在,把整个画面擦掉,用红色重新画一遍。
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const dist = Math.hypot(x - 100, y - 100);
if (dist < 80) {
ctx.clearRect(0, 0, 200, 200);
// ……用红色重新画所有东西
}
});
麻烦吗?确实麻烦。但换来的是完全的掌控力和极高的性能。
(3)放大会模糊
因为是像素,把 Canvas 拉伸放大,图像就会变糊。在高清屏(比如 Retina 屏)上,还需要根据 window.devicePixelRatio 手动把画布的实际像素调大,否则看起来会发虚。这是新手很常踩的坑。
(4)能画"像素级"的东西
Canvas 可以直接读取和修改每一个像素的颜色(getImageData / putImageData)。图片滤镜、马赛克、取色器、视频截帧这类功能,都是它的拿手好戏。
四、核心差异:一张表看明白
| 对比维度 | SVG | Canvas |
|---|---|---|
| 图形本质 | 矢量,数学描述 | 位图,像素点 |
| 绘制方式 | 声明式:描述"画面是什么" | 命令式:一步步下指令"怎么画" |
| 在页面中的存在 | 每个图形都是 DOM 节点 | 只有一个 <canvas> 节点 |
| 缩放效果 | 无限清晰 | 放大会模糊 |
| 事件交互 | 原生支持,直接绑定到图形上 | 需自己计算点击位置 |
| 修改图形 | 改属性即可,浏览器自动重绘 | 通常要擦掉重画 |
| CSS 样式 | 支持 | 不支持(只能整体设置画布样式) |
| 图形数量多时 | 节点越多越卡 | 依旧流畅 |
| 像素级处理 | 不擅长 | 擅长 |
| 可访问性 / SEO | 较好 | 较差,需额外处理 |
| 导出图片 | 可导出为 SVG 文件,或转为位图 | 可直接导出 PNG/JPEG |
这里面有两点特别值得展开说。
差异一:声明式 vs 命令式
- SVG 是"点菜":你告诉服务员"我要一份番茄炒蛋",至于怎么炒,厨房(浏览器)负责。
- Canvas 是"自己下厨":先打蛋,再切番茄,热锅放油……每一步都得你来。
点菜省心,但只能点菜单上有的;自己下厨辛苦,但想怎么做就怎么做。
差异二:性能的"拐点"
这是选型时最关键的考量。
SVG 的每一个图形都是 DOM 节点,浏览器需要为它们维护样式、布局、事件等一大堆信息。图形少的时候,这点开销可以忽略;可一旦图形数量达到成千上万,比如一张有 5 万个散点的图表,页面就会明显卡顿。
Canvas 则不在乎你画了多少东西,它只关心"画布有多大"。画 10 个圆和画 10 万个圆,最终都只是一张图。所以在海量图形、高频刷新的场景下,Canvas 优势明显。
反过来,如果画布很大(比如全屏 4K)、但图形很少,Canvas 每次重绘整个画布反而可能不如 SVG 划算。
可以粗略记成一句话:
图形少、要交互、要清晰 → SVG;图形多、刷新快、要像素操作 → Canvas。
五、它们的联系:不是对手,而是搭档
说了这么多区别,SVG 和 Canvas 其实并不是你死我活的关系,它们之间有不少联系。
1. 都是浏览器提供的原生绘图能力
二者都不需要任何插件,现代浏览器全部支持。它们共同取代了当年的 Flash,成为网页图形的两大支柱。
2. 绘图概念高度相通
学会一个,另一个就会上手很快。它们共享很多相同的概念:
- 坐标系:都是左上角为原点,x 向右,y 向下;
- 路径(Path):直线、贝塞尔曲线、圆弧;
- 填充(fill)和描边(stroke);
- 渐变、变换(平移、旋转、缩放)、透明度等。
比如 SVG 里画路径用的 d 属性字符串,Canvas 可以通过 Path2D 直接"拿来就用":
const path = new Path2D('M10 10 L100 10 L55 80 Z'); // 和 SVG 的 d 属性写法一样
ctx.fill(path);
3. 可以互相转换
- SVG → Canvas:把 SVG 当作一张图片,用
ctx.drawImage()画到 Canvas 上。很多"把网页图表导出为 PNG"的功能就是这么做的。 - Canvas → SVG:有一些库可以把 Canvas 的绘图命令"录下来",翻译成 SVG 代码。
4. 可以叠在一起用
在实际项目里,二者混用非常常见。比如一个地图应用:
- 底层用 Canvas 绘制成千上万个地块和道路,保证流畅;
- 上层用 SVG(或普通 DOM)放少量可点击的标记点、提示框,方便交互。
各取所长,效果最好。
5. 很多图表库两种都支持
像 ECharts 这样的知名图表库,就同时提供 Canvas 和 SVG 两种渲染模式,开发者只需改一个配置项即可切换。这也说明:在上层看来,它们只是同一件事的两种实现方式。
六、到底该怎么选?给你几个典型场景
| 场景 | 推荐 | 原因 |
|---|---|---|
| 网站 Logo、图标 | SVG | 体积小、无限清晰、能用 CSS 改颜色 |
| 简单的柱状图、饼图 | SVG | 数据量小,交互方便 |
| 流程图、组织架构图 | SVG | 节点要拖拽、点击、编辑 |
| 动画插画、路径描边动画 | SVG | CSS 动画配合起来很优雅 |
| 几万个点的散点图、热力图 | Canvas | 数据量大,SVG 会卡 |
| 网页游戏 | Canvas | 每秒几十帧的高频重绘 |
| 在线涂鸦、白板 | Canvas | 自由绘制,笔迹众多 |
| 图片编辑、滤镜、截图 | Canvas | 需要像素级操作 |
| 3D 场景、可视化大屏 | Canvas(WebGL/WebGPU) | 需要 GPU 加速 |
最后一行值得多说一句:Canvas 除了常用的 2D 画笔(getContext('2d')),还能通过 getContext('webgl')、getContext('webgpu') 调用显卡进行 3D 绘图。Three.js 这类 3D 库,底层画布就是 Canvas。此外,借助 OffscreenCanvas,还能把繁重的绘图工作搬到 Web Worker 里,避免卡住页面。这些都是 Canvas 在"高性能"方向上的延伸,SVG 并没有对应的能力。
七、一句话总结
- SVG 像一位有条理的管家:每样东西都登记在册,你说改哪个就改哪个,适合精致、需要交互的图形。
- Canvas 像一位手速飞快的画家:不记得画过什么,但画得又快又多,适合海量、高频、像素级的绘制。
两者没有谁好谁坏,只有合不合适。下次在项目里要"画点什么"的时候,先问自己三个问题:
- 要画的东西多不多?
- 需不需要频繁交互?
- 需不需要无限放大或像素级处理?
想清楚这三点,选择哪支画笔,答案自然就出来了。