数据可视化选型:ECharts 与自研 Canvas
中后台图表选型决策树、ECharts 主题与性能、Canvas 自绘适用场景与混合架构。
Canvas 绘制优化、WebGL 渲染管线、OffscreenCanvas 与大数据量图形渲染的性能策略。
可视化是高级前端的重要方向。Canvas 和 WebGL 的性能优化不同于 DOM 操作,需要理解像素级渲染的特殊性。
// ❌ 每次循环都设置样式
for (const point of points) {
ctx.fillStyle = point.color;
ctx.beginPath();
ctx.arc(point.x, point.y, 3, 0, Math.PI * 2);
ctx.fill();
}
// ✅ 按颜色分组批量绘制
const groups = groupBy(points, "color");
for (const [color, group] of Object.entries(groups)) {
ctx.fillStyle = color;
ctx.beginPath();
for (const p of group) ctx.arc(p.x, p.y, 3, 0, Math.PI * 2);
ctx.fill();
}
只重绘变化区域,而非整个 Canvas:
class DirtyRectRenderer {
private dirtyRegions: Rect[] = [];
markDirty(rect: Rect) {
this.dirtyRegions.push(rect);
}
render() {
if (this.dirtyRegions.length === 0) return;
const bounds = mergeRects(this.dirtyRegions);
ctx.clearRect(bounds.x, bounds.y, bounds.w, bounds.h);
this.drawRegion(bounds);
this.dirtyRegions = [];
}
}
将绘制逻辑移到 Worker,避免阻塞主线程:
// main.ts
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker("render-worker.js");
worker.postMessage({ canvas: offscreen }, [offscreen]);
// render-worker.js
self.onmessage = (e) => {
const ctx = e.data.canvas.getContext("2d");
// 在 Worker 中绘制,不阻塞 UI
};
JavaScript → Vertex Shader → Rasterization → Fragment Shader → Framebuffer
(顶点处理) (光栅化) (像素着色) (输出)
// 1. 创建 Shader Program
const program = createProgram(vertexShaderSource, fragmentShaderSource);
gl.useProgram(program);
// 2. 创建 Buffer 并传入顶点数据
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 3. 设置 attribute 并绘制
const posLoc = gl.getAttribLocation(program, "a_position");
gl.enableVertexAttribArray(posLoc);
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, vertexCount);
| 维度 | Canvas 2D | WebGL |
|---|---|---|
| 学习曲线 | 低 | 高 |
| 性能上限 | 中(万级元素) | 高(百万级顶点) |
| 3D 支持 | 无 | 原生 |
| 适用场景 | 图表、标注、简单动画 | 3D、粒子、大数据可视化 |
| 库生态 | ECharts, Fabric.js | Three.js, PixiJS, regl |
选型建议:
// WebGL Instanced Drawing — 一次绘制 10000 个矩形
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, 10000);
// 使用 stats.js 监控 FPS
const stats = new Stats();
document.body.appendChild(stats.dom);
function animate() {
stats.begin();
render();
stats.end();
requestAnimationFrame(animate);
}
目标基准:
| 方案 | FPS (Chrome M1) | 适用 |
|---|---|---|
| ECharts canvas | 48–52 | < 5k 点、要快上线 |
| ECharts GL | 55–58 | 中等 3D |
| 自研 WebGL instancing | 60 | > 10k 点、定制交互 |
我们 Dashboard 5k 点以下 ECharts,实时行情 50k 点 WebGL;切换阈值在 8k 点做过 A/B,WebGL CPU 占用低 30%。