SVG和Canvas,前端里的两支“画笔”,用的时候怎么选择?

32 阅读9分钟

打开一个数据大屏,满屏是跳动的折线图;打开一个在线白板,你可以随手涂鸦;打开一个网页小游戏,角色在屏幕上跑来跑去……这些画面背后,往往是前端里的两支"画笔":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 可没法帮你。你得自己做这些事:

  1. 监听整个画布的点击事件;
  2. 拿到鼠标坐标;
  3. 自己算一算这个点在不在圆里(点到圆心距离是否小于半径);
  4. 如果在,把整个画面擦掉,用红色重新画一遍。
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)。图片滤镜、马赛克、取色器、视频截帧这类功能,都是它的拿手好戏。


四、核心差异:一张表看明白

对比维度SVGCanvas
图形本质矢量,数学描述位图,像素点
绘制方式声明式:描述"画面是什么"命令式:一步步下指令"怎么画"
在页面中的存在每个图形都是 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节点要拖拽、点击、编辑
动画插画、路径描边动画SVGCSS 动画配合起来很优雅
几万个点的散点图、热力图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 像一位手速飞快的画家:不记得画过什么,但画得又快又多,适合海量、高频、像素级的绘制。

两者没有谁好谁坏,只有合不合适。下次在项目里要"画点什么"的时候,先问自己三个问题:

  1. 要画的东西多不多?
  2. 需不需要频繁交互?
  3. 需不需要无限放大或像素级处理?

想清楚这三点,选择哪支画笔,答案自然就出来了。