ChartJS加载大量数据时内存溢出及保留图表的内存清理方案咨询
问题原因排查
你目前的内存溢出问题核心是每次调用barChart函数都会新建一个Chart实例,旧实例没有被正确销毁,相关的数据集、事件监听、Canvas上下文资源会持续占用内存,累计到一定阈值就会触发Chrome的内存不足报错。
解决方案
分两种场景适配:
场景1:不需要保留图表交互能力(不需要hover提示、点击交互等)
这种场景可以完美实现「释放内存+保留图表显示」的需求,思路是将渲染完成的图表转为静态图片,再销毁Chart实例:
// 全局变量存实例,也可以存在window对象上避免污染全局 let barChartInstance = null; function barChart(barChartVal) { const chartDom = document.getElementById("chart-area-last-5-runs"); // 先销毁已存在的旧实例 if (barChartInstance) { barChartInstance.destroy(); barChartInstance = null; } const color = new chartColor(); barChartInstance = new Chart(chartDom, { type: 'bar', data: { labels: [barChartVal[5][0], barChartVal[5][1], barChartVal[5][2], barChartVal[5][3], barChartVal[5][4]], datasets: [{ data: [barChartVal[0][0], barChartVal[0][1], barChartVal[0][2], barChartVal[0][3], barChartVal[0][4]], label: 'Passed', backgroundColor: color.green(), }, { data: [barChartVal[1][0], barChartVal[1][1], barChartVal[1][2], barChartVal[1][3], barChartVal[1][4]], label: 'Failed', backgroundColor: color.red(), }, { data: [barChartVal[2][0], barChartVal[2][1], barChartVal[2][2], barChartVal[2][3], barChartVal[2][4]], label: 'Error', backgroundColor: color.yellow(), }, { data: [barChartVal[3][0], barChartVal[3][1], barChartVal[3][2], barChartVal[3][3], barChartVal[3][4]], label: 'Not Run', backgroundColor: color.blue(), }, { data: [barChartVal[4][0], barChartVal[4][1], barChartVal[4][2], barChartVal[4][3], barChartVal[4][4]], label: 'Not Applicable', backgroundColor: color.black(), }] }, options: { scales: { xAxes: [{ stacked: true, ticks: { display: false } }], yAxes: [{ stacked: true }] }, tooltips: { mode: 'index', intersect: false, titleFontSize: 14, bodyFontSize: 13, bodySpacing: 0, titleSpacing: 0, xPadding: 2, yPadding: 2, cornerRadius: 2, titleMarginBottom: 2 }, hover: { mode: 'index', intersect: false }, animation: false, parsing: false, normalized: true }, // 渲染完成钩子 plugins: [{ afterRender: function(chart) { // 转成静态图片 const imgUrl = chart.toBase64Image(); // 替换canvas为img const img = document.createElement('img'); img.src = imgUrl; img.alt = "统计柱状图"; img.style.width = chart.width + 'px'; img.style.height = chart.height + 'px'; chartDom.parentNode.replaceChild(img, chartDom); // 销毁实例释放所有内存 chart.destroy(); barChartInstance = null; } }] }); }
场景2:需要保留图表交互能力
这种情况不能销毁实例,需要通过单实例管理避免内存泄露:
let barChartInstance = null; function barChart(barChartVal) { const chartDom = document.getElementById("chart-area-last-5-runs"); const color = new chartColor(); const chartData = { labels: [barChartVal[5][0], barChartVal[5][1], barChartVal[5][2], barChartVal[5][3], barChartVal[5][4]], datasets: [{ data: [barChartVal[0][0], barChartVal[0][1], barChartVal[0][2], barChartVal[0][3], barChartVal[0][4]], label: 'Passed', backgroundColor: color.green(), }, { data: [barChartVal[1][0], barChartVal[1][1], barChartVal[1][2], barChartVal[1][3], barChartVal[1][4]], label: 'Failed', backgroundColor: color.red(), }, { data: [barChartVal[2][0], barChartVal[2][1], barChartVal[2][2], barChartVal[2][3], barChartVal[2][4]], label: 'Error', backgroundColor: color.yellow(), }, { data: [barChartVal[3][0], barChartVal[3][1], barChartVal[3][2], barChartVal[3][3], barChartVal[3][4]], label: 'Not Run', backgroundColor: color.blue(), }, { data: [barChartVal[4][0], barChartVal[4][1], barChartVal[4][2], barChartVal[4][3], barChartVal[4][4]], label: 'Not Applicable', backgroundColor: color.black(), }] }; // 实例已存在则直接更新数据,不需要新建 if (barChartInstance) { barChartInstance.data = chartData; barChartInstance.update(); return; } // 不存在则新建实例 barChartInstance = new Chart(chartDom, { type: 'bar', data: chartData, options: { scales: { xAxes: [{ stacked: true, ticks: { display: false } }], yAxes: [{ stacked: true }] }, tooltips: { mode: 'index', intersect: false, titleFontSize: 14, bodyFontSize: 13, bodySpacing: 0, titleSpacing: 0, xPadding: 2, yPadding: 2, cornerRadius: 2, titleMarginBottom: 2 }, hover: { mode: 'index', intersect: false }, animation: false, parsing: false, normalized: true } }); } // 可选:监听标签页隐藏事件临时销毁实例,切回时重新渲染,进一步降低后台内存占用 document.addEventListener('visibilitychange', () => { if (document.hidden && barChartInstance) { // 隐藏时销毁 barChartInstance.destroy(); barChartInstance = null; } else if (!document.hidden) { // 显示时重新调用barChart传入最新数据即可 // barChart(最新的barChartVal); } });
额外优化建议
- 大数据量场景下可以对数据集做降采样,减少单图表的渲染数据量
- 确认不需要的交互、动画功能都关闭,降低运行时内存开销
内容的提问来源于stack exchange,提问作者Mads Sander Høgstrup
相关产品推荐
相关产品推荐

