渲染卡死不再怕,offScreenCanvas来帮你
04/25/2024
背景
在我们的业务系统中,大量使用了 ECharts 来展示各类数据可视化图表。随着业务的发展,有些图表需要展示的数据量越来越大,导致在渲染过程中出现严重的性能问题,甚至造成页面卡死的情况。
为了解决这个问题,我们需要将复杂的计算和渲染过程从主线程中分离出去。Web Worker 是一个很好的选择,它可以在后台线程中进行复杂计算而不影响主线程的运行。而 OffscreenCanvas 则提供了在 Web Worker 中进行canvas渲染的能力,这正是我们需要的解决方案。
OffscreenCanvas 是什么?
OffscreenCanvas 是一个可以在非主线程(如 Web Worker)中使用的 Canvas API。它提供了与普通 Canvas 几乎相同的功能,但最大的区别是它可以在后台进行渲染,不会阻塞主线程的运行。
主要特点:
- 可以在 Web Worker 中进行渲染操作
- 支持 2D 和 WebGL 上下文
- 通过
transferControlToOffscreen()方法将 canvas 控制权转移到 Worker 线程 - 渲染结果可以通过
transferToImageBitmap()方法传回主线程
实现方案
1. 主线程代码
首先,我们需要创建一个 canvas 元素,并将其转换为 OffscreenCanvas:
// main.js
const canvas = document.querySelector('#myChart');
const offscreen = canvas.transferControlToOffscreen();
// 创建 Worker
const worker = new Worker('worker.js');
// 将 offscreenCanvas 传递给 Worker
worker.postMessage({
type: 'init',
canvas: offscreen,
width: canvas.clientWidth,
height: canvas.clientHeight
}, [offscreen]);
// 发送数据更新
function updateChart(data) {
worker.postMessage({
type: 'update',
data: data
});
}2. Worker 线程代码
在 Worker 中,我们需要初始化 ECharts 并处理来自主线程的消息:
// worker.js
importScripts('https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js');
let myChart = null;
self.onmessage = function (evt) {
const { type, canvas, width, height, data } = evt.data;
if (type === 'init') {
// 初始化 ECharts 实例
myChart = echarts.init(canvas, null, {
width: width,
height: height
});
// 设置基础配置
const option = {
// ... 图表配置
};
myChart.setOption(option);
}
if (type === 'update') {
// 更新数据
myChart.setOption({
series: [{
data: data
}]
});
}
};3. 使用示例
// 在 React 组件中使用
import React, { useEffect, useRef } from 'react';
function EChartsOffscreen() {
const canvasRef = useRef(null);
const workerRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
const offscreen = canvas.transferControlToOffscreen();
workerRef.current = new Worker('/worker.js');
workerRef.current.postMessage({
type: 'init',
canvas: offscreen,
width: canvas.clientWidth,
height: canvas.clientHeight
}, [offscreen]);
// 清理函数
return () => {
workerRef.current?.terminate();
};
}, []);
return <canvas ref={canvasRef} style={{ width: '100%', height: '400px' }} />;
}
export default EChartsOffscreen;注意事项
-
浏览器兼容性:
- OffscreenCanvas 目前主要在现代浏览器中支持
- 建议添加降级处理方案
-
数据传输开销:
- Worker 线程与主线程之间的数据传输存在开销
- 对于频繁更新的场景,需要考虑数据传输的性能影响
-
内存管理:
- 及时清理不再使用的 Worker 和资源
- 注意避免内存泄漏
性能对比
在我们的实际项目中,使用 OffscreenCanvas 后的性能提升显著:
-
渲染 10 万数据点的折线图:
- 传统方案:主线程阻塞 2-3 秒
- OffscreenCanvas:主线程无阻塞,总耗时减少 40%
-
多图表同时渲染:
- 传统方案:明显卡顿,页面响应迟缓
- OffscreenCanvas:保持流畅,用户体验大幅提升
总结
通过使用 OffscreenCanvas 和 Web Worker,我们成功解决了大数据量图表渲染导致的性能问题。这个方案不仅提升了用户体验,还为后续更多数据可视化场景提供了可靠的技术支持。
虽然实现过程相对复杂一些,但带来的性能提升是值得的。建议在以下场景考虑使用 OffscreenCanvas:
- 大数据量的图表渲染
- 需要频繁更新的动态图表
- 多个复杂图表同时展示的场景