渲染引擎 (Canvas/SVG)
ECharts 底层渲染引擎详解:Canvas 与 SVG 的选择与优化
📖 概述
ECharts 基于轻量级 Canvas 库 ZRender 实现,支持两种渲染模式:
- Canvas: 默认渲染方式,适合大数据量和复杂动画
- SVG: 可选渲染方式,适合小数据量和高清晰度需求
理解两种渲染方式的差异和适用场景,是性能优化的第一步。
🎨 Canvas vs SVG 对比
核心差异
| 特性 | Canvas | SVG |
|---|---|---|
| 渲染方式 | 像素级渲染(位图) | 矢量图形(DOM) |
| 性能 | 大数据量性能优异 | 小数据量性能良好 |
| 内存占用 | 较低 | 较高(每个元素都是 DOM 节点) |
| 文件大小 | 导出图片体积小 | 导出 SVG 体积大 |
| 缩放清晰度 | 放大后模糊 | 无限缩放不失真 |
| 事件绑定 | 需要手动计算坐标 | 原生 DOM 事件支持 |
| SEO 友好 | ❌ 不支持 | ✅ 支持 |
| 浏览器兼容 | IE9+ | IE9+ |
| 适用场景 | 大数据、实时数据、动画 | 小数据、打印、无障碍 |
性能对比测试
javascript
// 测试数据量:10,000 个数据点
const data = Array.from({ length: 10000 }, (_, i) => [i, Math.random()]);
// Canvas 渲染
const canvasChart = echarts.init(container, null, { renderer: 'canvas' });
canvasChart.setOption({
series: [{ type: 'scatter', data, symbolSize: 2 }]
});
// ✅ 渲染时间: ~50ms,内存占用: ~15MB
// SVG 渲染
const svgChart = echarts.init(container, null, { renderer: 'svg' });
svgChart.setOption({
series: [{ type: 'scatter', data, symbolSize: 2 }]
});
// ❌ 渲染时间: ~800ms,内存占用: ~120MB1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
结论: 数据量 > 1000 时,Canvas 性能优势明显
🔧 如何切换渲染模式
初始化时指定
javascript
// 使用 Canvas 渲染器(默认)
const chart = echarts.init(document.getElementById('main'), null, {
renderer: 'canvas' // 或 'svg'
});1
2
3
4
2
3
4
根据场景动态选择
javascript
function createChart(container, dataLength) {
const renderer = dataLength > 1000 ? 'canvas' : 'svg';
return echarts.init(container, null, {
renderer,
devicePixelRatio: window.devicePixelRatio || 1
});
}
// 使用
const chart = createChart(domElement, dataArray.length);1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
Vue/React 中的实践
typescript
// React Hook
import { useEffect, useRef } from 'react';
import * as echarts from 'echarts';
function useECharts(data: any[]) {
const chartRef = useRef<HTMLDivElement>(null);
const chartInstance = useRef<echarts.ECharts>();
useEffect(() => {
if (chartRef.current) {
// 根据数据量自动选择渲染器
const renderer = data.length > 1000 ? 'canvas' : 'svg';
chartInstance.current = echarts.init(chartRef.current, null, {
renderer
});
return () => {
chartInstance.current?.dispose();
};
}
}, [data]);
return chartRef;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
🚀 Canvas 渲染优化
1. 开启大数据模式
javascript
chart.setOption({
series: [{
type: 'scatter',
data: largeData, // 10万+数据
large: true, // ✅ 开启大数据优化
largeThreshold: 2000, // 阈值,默认2000
symbolSize: 2
}]
});1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
原理:
- 关闭单个元素的交互
- 简化绘制逻辑
- 批量渲染
2. 降低采样精度
javascript
chart.setOption({
series: [{
type: 'line',
data: hugeData, // 百万级数据
sampling: 'lttb', // ✅ LTTB 降采样算法
symbol: 'none' // 关闭数据点标记
}]
});1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
可用算法:
'lttb': Largest Triangle Three Buckets(推荐)'average': 平均值采样'max': 最大值采样'min': 最小值采样
3. 关闭阴影和特效
javascript
chart.setOption({
series: [{
type: 'bar',
data: data,
itemStyle: {
shadowBlur: 0, // ✅ 关闭阴影
shadowColor: 'transparent'
},
emphasis: {
itemStyle: {
shadowBlur: 0 // ✅ 高亮时也关闭
}
}
}]
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
4. 使用渐进式渲染
javascript
chart.setOption({
progressive: 1000, // ✅ 每帧渲染1000个元素
progressiveThreshold: 5000, // 超过5000启用渐进式
series: [{
type: 'scatter',
data: largeData
}]
});1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
效果: 避免长时间阻塞主线程,提升用户体验
🎯 SVG 渲染优化
1. 适用场景判断
javascript
function shouldUseSVG(dataLength, requirements) {
return (
dataLength < 1000 && // 数据量小
(requirements.needPrint || // 需要打印
requirements.needSEO || // 需要SEO
requirements.needAccessibility) // 需要无障碍
);
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
2. 减少 DOM 节点
javascript
chart.setOption({
series: [{
type: 'pie',
data: smallData,
label: {
show: false // ✅ 关闭标签减少DOM节点
},
emphasis: {
label: {
show: true // 只在需要时显示
}
}
}]
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
3. 优化 CSS
css
/* ✅ 为 SVG 图表添加硬件加速 */
.echarts-svg {
transform: translateZ(0);
will-change: transform;
}
/* ✅ 禁用不必要的过渡动画 */
.echarts-svg * {
transition: none !important;
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
4. SEO 优化
javascript
// SVG 可以被搜索引擎索引
const svgChart = echarts.init(container, null, { renderer: 'svg' });
// 添加语义化信息
svgChart.setOption({
title: {
text: '2024年销售数据统计', // ✅ 搜索引擎可读
subtext: '数据来源:公司CRM系统'
},
aria: {
enabled: true, // ✅ 开启无障碍支持
decal: {
show: true
}
}
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
🔍 渲染引擎源码解析
ZRender 架构
ECharts
↓
ZRender (渲染引擎)
↓
├── Canvas Painter (Canvas 渲染器)
│ ├── Layer Manager (图层管理)
│ ├── Path Renderer (路径渲染)
│ └── Image Renderer (图片渲染)
│
└── SVG Painter (SVG 渲染器)
├── SVG Element Creator
├── Event Handler
└── Style Manager1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
核心流程
javascript
// 1. 初始化渲染器
const zr = zrender.init(dom, {
renderer: 'canvas', // 或 'svg'
width: 800,
height: 600
});
// 2. 创建图形元素
const circle = new zrender.Circle({
shape: { cx: 100, cy: 100, r: 50 },
style: { fill: '#5470c6' }
});
// 3. 添加到渲染树
zr.add(circle);
// 4. 触发重绘
zr.refresh();1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Canvas 渲染流程
javascript
// 伪代码展示 Canvas 渲染核心逻辑
class CanvasPainter {
render() {
const ctx = this.ctx;
// 1. 清空画布
ctx.clearRect(0, 0, width, height);
// 2. 遍历所有图形元素
for (const el of this.storage.getDisplayList()) {
// 3. 应用变换
ctx.save();
ctx.transform(el.transform);
// 4. 绘制路径
el.buildPath(ctx, el.shape);
ctx.fill();
// 5. 恢复状态
ctx.restore();
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SVG 渲染流程
javascript
// 伪代码展示 SVG 渲染核心逻辑
class SVGPainter {
render() {
// 1. 创建 SVG 容器
const svg = createElementNS('http://www.w3.org/2000/svg', 'svg');
// 2. 遍历所有图形元素
for (const el of this.storage.getDisplayList()) {
// 3. 创建对应的 SVG 元素
const path = createElementNS('http://www.w3.org/2000/svg', 'path');
// 4. 设置属性
path.setAttribute('d', el.buildPath());
path.setAttribute('fill', el.style.fill);
// 5. 绑定事件
path.addEventListener('click', el.onclick);
// 6. 添加到 DOM
svg.appendChild(path);
}
this.dom.innerHTML = '';
this.dom.appendChild(svg);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
📊 实际案例对比
案例 1: 实时股票行情 (10万数据点)
javascript
// ❌ SVG 渲染 - 卡顿严重
const svgChart = echarts.init(dom, null, { renderer: 'svg' });
svgChart.setOption({
series: [{
type: 'line',
data: stockData, // 100,000个点
animation: false
}]
});
// 结果: 渲染耗时 2.3s,内存占用 180MB
// ✅ Canvas 渲染 - 流畅运行
const canvasChart = echarts.init(dom, null, { renderer: 'canvas' });
canvasChart.setOption({
series: [{
type: 'line',
data: stockData,
large: true, // 开启大数据模式
sampling: 'lttb', // 降采样
animation: false
}]
});
// 结果: 渲染耗时 85ms,内存占用 22MB1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
案例 2: 企业 Logo 展示 (需要打印)
javascript
// ❌ Canvas 渲染 - 打印模糊
const canvasChart = echarts.init(dom, null, { renderer: 'canvas' });
// 打印到 PDF 后,放大出现锯齿
// ✅ SVG 渲染 - 完美打印
const svgChart = echarts.init(dom, null, { renderer: 'svg' });
svgChart.setOption({
graphic: {
elements: [{
type: 'image',
style: { image: '/logo.svg' }
}]
}
});
// 打印到 PDF 后,任意缩放都清晰1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
案例 3: 无障碍地图 (SEO + 可访问性)
javascript
// ✅ SVG + ARIA 支持
const mapChart = echarts.init(dom, null, { renderer: 'svg' });
mapChart.setOption({
series: [{
type: 'map',
map: 'china',
data: provinceData
}],
aria: {
enabled: true,
label: {
name: '中国各省人口分布图'
},
decal: {
show: true,
dataSource: {
type: 'pattern',
color: '#ddd',
dashArrayX: [1, 5],
dashArrayY: [5]
}
}
}
});
// 结果: 屏幕阅读器可以读取,搜索引擎可以索引1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
⚠️ 常见陷阱
陷阱 1: 忘记指定渲染器导致性能问题
javascript
// ❌ 错误:处理大数据时使用默认 Canvas,但未开启优化
const chart = echarts.init(dom); // 默认 canvas
chart.setOption({
series: [{
type: 'scatter',
data: hugeData // 50万数据
// 未开启 large 模式,导致卡顿
}]
});
// ✅ 正确:明确配置优化选项
const chart = echarts.init(dom, null, { renderer: 'canvas' });
chart.setOption({
series: [{
type: 'scatter',
data: hugeData,
large: true,
largeThreshold: 2000,
progressive: 5000
}]
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
陷阱 2: SVG 渲染大量数据导致内存泄漏
javascript
// ❌ 错误:用 SVG 渲染10万个散点
const chart = echarts.init(dom, null, { renderer: 'svg' });
chart.setOption({
series: [{
type: 'scatter',
data: Array.from({ length: 100000 })
}]
});
// 结果: 创建10万个 DOM 节点,浏览器崩溃
// ✅ 正确:数据量大时使用 Canvas
const chart = echarts.init(dom, null, {
renderer: data.length > 1000 ? 'canvas' : 'svg'
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
陷阱 3: 混合使用时配置错误
javascript
// ❌ 错误:在 SVG 模式下使用 Canvas 特有配置
const chart = echarts.init(dom, null, { renderer: 'svg' });
chart.setOption({
series: [{
type: 'line',
data: data,
progressive: 1000 // ⚠️ progressive 只对 Canvas 生效
}]
});
// ✅ 正确:根据渲染器调整配置
const isCanvas = renderer === 'canvas';
chart.setOption({
series: [{
type: 'line',
data: data,
...(isCanvas && { progressive: 1000 })
}]
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
🎯 最佳实践总结
选择指南
配置模板
javascript
// Canvas 高性能配置模板
const canvasConfig = {
renderer: 'canvas',
series: [{
type: 'scatter',
data: largeData,
large: true,
largeThreshold: 2000,
progressive: 5000,
progressiveThreshold: 10000,
sampling: 'lttb',
animation: false,
symbolSize: 2
}]
};
// SVG 高质量配置模板
const svgConfig = {
renderer: 'svg',
aria: {
enabled: true,
decal: { show: true }
},
series: [{
type: 'pie',
data: smallData,
animation: true,
animationDuration: 1000
}]
};1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
📚 延伸阅读
- ZRender 官方文档
- Canvas API MDN
- SVG MDN
- 性能优化 - 大数据 large 模式
- 部署导出 - SVG 导出
最后更新: 2026-04-22
难度等级: ⭐⭐⭐
预计阅读时间: 25 分钟
示例演示
{
"title": {
"text": "2024年销售数据统计",
"subtext": "数据来源:公司CRM系统"
},
"aria": {
"enabled": true,
"decal": {
"show": true
}
}
}示例演示
{
"graphic": {
"elements": [
{
"type": "image",
"style": {
"image": "/logo.svg"
}
}
]
}
}