Skip to content

SVG 与 canvas 对比

SVG 和 Canvas 是 HTML5 中两种主要的图形技术,各有优势和适用场景。本节将从技术特性、性能、无障碍、适用场景等多个维度对比两者,帮助你在项目中做出正确的选择。

前置知识

阅读本节前,建议先了解:SVG 内联矢量图

基础概念

SVG(Scalable Vector Graphics)是一种基于 XML 的矢量图形格式,它描述图形的数学定义(路径、形状、颜色),浏览器根据定义实时渲染。Canvas 是一个位图绘图表面,通过 JavaScript 命令逐像素绘制。

两者的核心区别在于:SVG 是声明式的(描述"是什么"),Canvas 是命令式的(告诉"怎么画")。这决定了它们在性能、交互性和适用场景上的根本差异。

详细说明

核心对比表

对比维度SVGCanvas
图形类型矢量图位图
描述方式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);
}

劣势:

  • 内容不可被搜索引擎索引
  • 屏幕阅读器无法识别绘制内容
  • 缩放会模糊
  • 没有自动的事件系统(需要手动实现碰撞检测等)

适用场景对比

场景推荐原因
图标/LogoSVG矢量缩放、CSS 样式化
数据图表SVG 或 Canvas简单图表用 SVG,大数据量用 Canvas
2D 游戏Canvas高性能实时渲染
地图SVG矢量缩放、交互式元素
图像处理Canvas像素级操作
动画 BannerCanvas流畅动画
流程图SVGDOM 可操作、可交互
粒子效果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

参考链接