大数据量下是选择Canvas还是SVG,Echarts最佳实践测试

2025年3月24日

笔者:ryanho

前段时间看了echarts官网给出来的最佳实践,想测试一下具体的性能差距。

原文:echarts.apache.org/handbook/zh...

  • 数据量较大(经验判断 > 1k)、较多交互时,建议选择 Canvas 渲染器。

我注意到这句,所以我们设计测试方案进行对比,来看一下具体结果。

1.设计测试方案

  1. 测试canvas和svg多实例(100个,1000个)。
  2. 测试canvas和svg大数据(10000数据量,100000数据量)。

2.创建项目

  1. 初始化项目

    sql 复制代码
    pnpm create vite@latest echarts-performance-test -- --template vanilla
    cd echarts-performance-test
    pnpm install
    pnpm install echarts
    ​
    启动
    pnpm run dev
  1. 代码部分

    index.html

    xml 复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>ECharts性能测试</title>
      <style>
        body { margin: 20px; font-family: Arial; }
        .controls { margin-bottom: 20px; }
        button { margin-right: 10px; padding: 8px 12px; cursor: pointer; }
        #charts-container { display: flex; flex-wrap: wrap; gap: 10px;  width: 100%;height: auto; }
        .chart-item { width: 300px; height: 200px; border: 1px solid #ddd; }
        #metrics { position: fixed; top: 20px; right: 20px; background: #f5f5f5; padding: 15px; }
      </style>
    </head>
    <body>
      <div class="controls">
        <button onclick="runTest('massInstances', 'canvas')">测试 Canvas 多实例</button>
        <button onclick="runTest('massInstances', 'svg')">测试 SVG 多实例</button>
        <button onclick="runTest('bigData', 'canvas')">测试 Canvas 大数据</button>
        <button onclick="runTest('bigData', 'svg')">测试 SVG 大数据</button>
        <button onclick="clearCharts()">清理所有图表</button>
        <label>实例数量: <input type="number" id="instanceCount" value="100"></label>
        <label>数据量级: <input type="number" id="dataSize" value="10000"></label>
      </div>
    ​
      <div id="charts-container"></div>
      
      <div id="metrics">
        <h3>性能指标</h3>
        <div id="fps">FPS: -</div>
        <div id="renderTime">渲染时间: -</div>
        <div id="status">状态: 空闲</div>
      </div>
    ​
      <script type="module" src="/main.js"></script>
    </body>
    </html>

    main.js

    ini 复制代码
    import * as echarts from 'echarts';
    ​
    // 全局状态
    let charts = [];
    let fpsLastTime = performance.now();
    let fpsFrames = 0;
    ​
    // 性能监控
    function startFPSMonitor() {
      function checkFPS() {
        const now = performance.now();
        const delta = now - fpsLastTime;
        fpsFrames++;
    ​
        if (delta >= 1000) {
          const fps = Math.round((fpsFrames * 1000) / delta);
          document.getElementById('fps').textContent = `FPS: ${fps}`;
          fpsLastTime = now;
          fpsFrames = 0;
        }
        requestAnimationFrame(checkFPS);
      }
      checkFPS();
    }
    ​
    // 创建测试图表(添加容器尺寸验证)
    function createChart(container, renderer, data) {
      // 确保容器已渲染且具有有效尺寸
      if (container.clientWidth === 0 || container.clientHeight === 0) {
        console.error('容器尺寸异常,请检查 DOM 可见性');
        return null;
      }
      
      const chart = echarts.init(container, null, { renderer });
      chart.setOption({
        dataset: { source: data },
        xAxis: { type: 'value' },
        yAxis: { type: 'value' },
        series: [{ type: 'scatter' }]
      });
      return chart;
    }
    ​
    // 大数据测试(添加延迟重试机制)
    function testBigData(renderer, dataSize) {
      clearCharts();
      const data = Array.from({ length: dataSize }, (_, i) => [i, Math.random() * 100]);
      
      const container = document.createElement('div');
      container.style.width = '100%';
      container.style.height = '400px';
      document.body.appendChild(container);
    ​
      // 延迟初始化确保容器已渲染
      setTimeout(() => {
        const startTime = performance.now();
        const chart = createChart(container, renderer, data);
        if (!chart) return;
    ​
        const renderTime = performance.now() - startTime;
        document.getElementById('renderTime').textContent = 
          `渲染时间: ${renderTime.toFixed(2)}ms`;
        charts.push(chart);
      }, 50);
    }
    ​
    // 多实例测试(添加容器预检)
    function testMassInstances(renderer, instanceCount) {
      clearCharts();
      const container = document.getElementById('charts-container');
      
      // 验证父容器可见性
      if (container.offsetParent === null) {
        console.error('charts-container 容器不可见');
        return;
      }
    ​
      const startTime = performance.now();
      for (let i = 0; i < instanceCount; i++) {
        const div = document.createElement('div');
        div.className = 'chart-item';
        div.style.width = '300px';
        div.style.height = '200px';  // 明确设置子容器尺寸
        container.appendChild(div);
        
        const chart = createChart(div, renderer, [[i % 10, Math.random() * 100]]);
        if (!chart) continue;
        charts.push(chart);
      }
      
      const renderTime = performance.now() - startTime;
      document.getElementById('renderTime').textContent = 
        `总渲染时间: ${renderTime.toFixed(2)}ms`;
    }
    ​
    // 清理图表
    function clearCharts() {
      charts.forEach(chart => chart.dispose());
      charts = [];
      const container = document.getElementById('charts-container');
      if (container) container.innerHTML = '';
    }
    ​
    // 统一测试入口(添加安全校验)
    function runTest(testType, renderer) {
      if (!document.getElementById('charts-container')) {
        console.error('找不到 charts-container 容器');
        return;
      }
    ​
      document.getElementById('status').textContent = '测试运行中...';
      
      const instanceCount = parseInt(document.getElementById('instanceCount').value) || 100;
      const dataSize = parseInt(document.getElementById('dataSize').value) || 10000;
    ​
      if (testType === 'massInstances') {
        testMassInstances(renderer, instanceCount);
      } else if (testType === 'bigData') {
        testBigData(renderer, dataSize);
      }
    ​
      setTimeout(() => {
        document.getElementById('status').textContent = '状态: 空闲';
      }, 500);
    }
    ​
    // 所有 DOM 相关操作在加载完成后执行
    document.addEventListener('DOMContentLoaded', () => {
      // 启动性能监控
      startFPSMonitor();
    ​
      // 挂载函数到 window 对象
      window.runTest = runTest;
      window.clearCharts = clearCharts;
    ​
      // 初始化状态显示
      document.getElementById('status').textContent = '状态: 就绪';
    ​
      // 确保测试容器可见
      const container = document.getElementById('charts-container');
      if (container) {
        container.style.display = 'flex';  // 显式设置布局
      }
    });
  2. 运行后页面如下图:

3.进行测试

在测试的过程中我发现,先测试 Canvas 多实例,再测试SVG多实例,SVG就会更快,反之亦然,所以为了避免误差,每次都重新打开页面进行测试。

3.1多实例测试100个

第一次测试:

canvas 332.30ms

svg 335.80ms

差距很小,canvas更快一点但是差距可以忽略不计。

第二次测试:

canvas 317.80ms

svg 342.40ms

这次可以看到canvas更快一些,后面多次测试也是canvas在100实例的测试中更快一些,实际使用时,人眼观察的差距可以忽略不计。

3.2多实例测试1000个

这次的性能开销更大了会有什么样的结果。

第一次测试:

canvas 2906.50ms

svg 3270.70ms

第二次测试:

canvas 2901.70ms

svg 3272.90ms

两次的测试基本保持同样的结果,在1000实例测试中canvas继续保持了性能的优势。

3.3大数据测试10000数据量

第一次测试:

canvas 50.10ms

svg 48.40ms

第二次测试:

canvas 48.70ms

svg 46.90ms

在大数据量的对比上svg两次的测试结果都更快了一些。

3.4大数据测试100000数据量

第一次测试:

canvas 94.40ms

svg 90.60ms

虽然svg计算出来的时间更快一些,但是svg无法通过gpu加速,所以FPS已经非常的低,实际上网页也处于了不可用状态。

4.结论

SVG 的 GPU 加速能力有限,主要依赖 CPU 进行矢量计算和 DOM 管理,而 Canvas 通过位图操作和 WebGL 能更高效利用 GPU。在需要高性能、大数据量或复杂动画的场景下,Canvas 是更优选择;SVG 则适用于对清晰度和矢量编辑有要求的场景。

在官方建议下,多实例和大数据量下选择canvas渲染器没毛病。

相关推荐
AlienZHOU2 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
Captaincc5 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师6 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen7 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒7 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
前端snow7 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林8187 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
JamesZhang800787 小时前
页面内存只涨不跌? 一次泄漏排查, 牵出 WeakMap 的诞生
前端
Z小明8 小时前
第 6 章 组件进阶
前端·vue.js
江华森8 小时前
HTTP请求的完整过程详解:从DNS解析到TCP挥手的微秒级实战分析
前端