大数据量下是选择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渲染器没毛病。

相关推荐
禁止摆烂_才浅17 分钟前
前端开发小技巧 - 【CSS】- 表单控件的 placeholder 如何控制换行显示?
前端·css·html
烂蜻蜓19 分钟前
深度解读 C 语言运算符:编程运算的核心工具
java·c语言·前端
PsG喵喵24 分钟前
用 Pinia 点燃 Vue 3 应用:状态管理革新之旅
前端·javascript·vue.js
鹏仔工作室24 分钟前
vue h5实现车牌号输入框
前端·javascript·vue.js
冴羽41 分钟前
SvelteKit 最新中文文档教程(11)—— 部署 Netlify 和 Vercel
前端·javascript·svelte
曹天骄1 小时前
react-hook-form 和 @tanstack/form 比较
前端·react.js·前端框架
IT、木易1 小时前
如何在 React 项目中进行服务器端渲染(SSR),它有什么优势
前端·react.js·前端框架
风清云淡_A1 小时前
【react18】react项目使用mock模拟后台接口
前端·react.js
知识分享小能手2 小时前
CSS3学习教程,从入门到精通,CSS3 定位布局页面知识点及案例代码(18)
前端·javascript·css·学习·html·css3·html5
Python私教2 小时前
Vue 在现代 Web 开发中的优势
前端·javascript·vue.js