性能优化与实例管理:destroy / update / decimation / OffscreenCanvas
基于 Chart.js 4.5.x · 核于 2026-07
速查
- 「Canvas is already in use」:同一 canvas 重复
new Chart必炸——先Chart.getChart(canvas)?.destroy(),或组件卸载钩子里chart.destroy()(SPA / 热更新最高频坑) destroy():复用 canvas 前必须调用,清理引用与事件监听——SPA 卸载必做,否则内存泄漏 + 重建报错Chart.getChart(key):按 canvas(id / 元素 / ctx)反查实例,找不到返回 undefined——重建前防重复 new 的钥匙Chart.instances:全部实例注册表(打印 resize 场景遍历用)- 数据更新范式:直接 push / pop
chart.data.labels与chart.data.datasets[i].data,然后chart.update() update(mode?):'none'(跳过动画)/'reset'/'resize'/'show'/'hide'/'active';也可传函数按 dataset 定制- 整对象替换:
chart.options支持整对象替换后再update() - 换轴注意:换轴 id / type 后旧
chart.scales引用失效 - spanGaps 数字形式:给毫秒数 = 仅跨越不超过该时间距离的 null 缺口
- 打印坑:打印时 resize 事件不触发——
onbeforeprint里遍历Chart.instances手动resize() resizeDelay:响应式 resize 的防抖毫秒数(默认 0),容器高频变化时的重绘阀门- 其他实例方法:
reset()(回初始动画前)、render()(重绘不重布局)、stop()(停动画)、clear()(清画布)、resize(w?, h?)(无参 = 适配容器) - 导出:
toBase64Image(type?, quality?)如('image/jpeg', 1);PNG 默认透明底——白底用背景插件(见插件页) - 显隐两套 API:dataset 级
setDatasetVisibility(i, bool)/hide(i, dataIndex?)/show(...)(hide/show 带过渡动画);数据条目级toggleDataVisibility(index)/getDataVisibility(index)(pie 用) - 程序化高亮:
setActiveElements([{ datasetIndex, index }]);查插件isPluginEnabled(id) - 元数据:
getDatasetMeta(i)/getSortedVisibleDatasetMetas()取元素级 meta - 性能手段 ① 禁动画:
animation: false→ 单次渲染 + 自动启用 Path2D 缓存 - 性能手段 ② decimation 降采样(line 专用,默认 disabled):
plugins.decimation: { enabled, algorithm, samples, threshold } - decimation 启用前提(五条全满足):dataset
indexAxis: 'x'、line 类型、x 轴 linear / time、parsing: false、数据点数超 threshold(默认 4×画布宽) - 算法取舍:
lttb保趋势大幅减点;min-max保峰值(噪声信号),每像素最多 4 点 - decimation 属 v4 内置插件:按需注册路线下别漏
Chart.register(Decimation) - 性能手段 ③:
parsing: false+ 内部格式喂数(数据必须已排序,如 category 轴内部是整数索引) - normalized: true 三前提:数据唯一、已排序、各 dataset 索引一致
- 性能手段 ④ 轴与刻度:指定
min/max免全量扫描算范围;ticks.sampleSize只测量子集;minRotation === maxRotation跳过旋转计算 - 性能手段 ⑤ line 专项:保持
tension: 0(默认)走自动路径抽稀(tension / stepped / borderDash 全默认时生效)、spanGaps: true省分段开销、showLine: false只画点、pointRadius: 0只画线 - 性能手段 ⑥ Web Worker + OffscreenCanvas:
canvas.transferControlToOffscreen()后 postMessage 给 worker 内 new Chart - Worker 三限制:配置里不能带函数、worker 无 DOM、resize 需手动
一、实例生命周期:destroy 与「Canvas is already in use」
Chart.js 一个 canvas 只能挂一个实例。SPA(Vue / React 热更新、路由切换、组件重建)里最高频的报错就是同一 canvas 重复 new Chart 导致的「Canvas is already in use」:
js
// 重建前:按 canvas(id/元素/ctx)反查旧实例,有则销毁
Chart.getChart(canvas)?.destroy(); // 找不到返回 undefined,可安全链式
const chart = new Chart(canvas, config);
// SPA 组件卸载钩子里必做,否则内存泄漏 + 下次重建报错
chart.destroy();destroy()会清理引用与事件监听,复用 canvas 前必须调用。- 静态注册表
Chart.instances收录全部存活实例——打印场景 resize 事件不触发,就靠onbeforeprint遍历它逐个resize()。
二、更新数据的正确姿势:update(mode)
官方 addData / removeData 范式——直接改 chart.data,然后 update():
js
// 加一条数据:labels 和每个 dataset 的 data 同步 push
chart.data.labels.push(label);
chart.data.datasets.forEach((dataset) => {
dataset.data.push(newData);
});
chart.update(); // 带动画重绘
// 删数据同理 pop 后 update
chart.update('none'); // 跳过动画(高频刷新场景)update(mode)全模式:'none'/'reset'/'resize'/'show'/'hide'/'active';还可传函数按 dataset 定制过渡。- 整对象替换
chart.options也支持;注意换轴 id / type 后旧chart.scales引用失效,要重新获取。
三、实例 API 速览
| 方法 | 要点 |
|---|---|
update(mode?) | 改完 data / options 后调用;'none' 跳过动画 |
destroy() | 复用 canvas 前必须调用(SPA 卸载必做) |
reset() | 回到初始动画前状态 |
render() | 重绘(不重新布局 / 更新数据) |
stop() | 停止当前动画(可链式) |
resize(w?, h?) | 手动改尺寸;无参 = 适配容器 |
clear() | 清空画布(可链式) |
toBase64Image(type?, quality?) | 导出图片,如 ('image/jpeg', 1);PNG 默认透明底 |
getElementsAtEventForMode(e, mode, options, useFinalPosition) | 事件 → 命中元素数组(见交互页) |
getDatasetMeta(i) / getSortedVisibleDatasetMetas() | 取元素级元数据 |
setDatasetVisibility(i, bool) / hide(i, dataIndex?) / show(...) | dataset 级显隐(hide / show 带过渡动画) |
toggleDataVisibility(index) / getDataVisibility(index) | 数据条目级显隐(pie / doughnut / polarArea 图例点击走这套) |
setActiveElements([{ datasetIndex, index }]) | 程序触发高亮 |
isPluginEnabled(id) | 检查插件是否启用 |
静态 Chart.getChart(key) | 按 canvas 反查实例——防重复 new 的钥匙 |
静态 Chart.instances | 全部实例注册表 |
四、性能优化清单(官方 Performance 页)
1. 禁动画
js
options: { animation: false } // 单次渲染,且自动启用 Path2D 缓存2. decimation 降采样插件(line 专用)
默认 disabled,启用前提五条全部满足:dataset indexAxis: 'x'、line 类型、x 轴 linear / time、parsing: false、数据点数超 threshold(默认 4×画布宽)——「配了没生效」几乎都是缺前提:
js
options: {
parsing: false, // 前提之一:关闭解析、按内部格式喂数
plugins: {
decimation: {
enabled: true,
algorithm: 'lttb' // 'lttb' 保趋势大幅减点;'min-max' 保峰值(噪声信号,每像素最多 4 点)
// 另有 samples、threshold 选项
}
}
}3. parsing: false + normalized: true
跳过解析直接吃内部格式数据(须已排序);数据唯一、已排序、各 dataset 索引一致时再加 normalized: true。
4. 轴与刻度
- 指定轴
min/max:免去全量扫描算范围。 ticks.sampleSize:只测量部分标签加速布局。minRotation === maxRotation:跳过旋转计算。
5. line 专项
- 保持
tension: 0(默认)走自动路径抽稀——tension / stepped / borderDash 全默认时生效。 spanGaps: true省分段开销(有 null 缺口时反而更快)。showLine: false只画点;pointRadius: 0只画线。
6. Web Worker + OffscreenCanvas
把渲染整个挪出主线程:
js
// 主线程:移交 canvas 控制权
const offscreen = canvas.transferControlToOffscreen();
worker.postMessage({ canvas: offscreen }, [offscreen]);
// worker 线程:对 OffscreenCanvas 正常 new Chart
onmessage = (event) => {
const chart = new Chart(event.data.canvas, config);
};三个限制:配置里不能带函数(postMessage 序列化不了)、worker 内无 DOM、resize 需手动处理。
下一页:参考 —— 图表类型 / 配置命名空间 / 实例 API / v2→v4 迁移映射速查表与易错点清单。