背景
在微信小程序中使用 ECharts 时,遇到了两个隐蔽的坑,花费了大量时间排查。记录在此,供后续开发参考。
坑一:Canvas 原生组件不跟随页面滚动
问题描述
小程序中的 <canvas> 是原生组件(Native Component),它的渲染层级高于普通 WXML 组件。当页面使用自定义滚动容器(如 overflow-y: auto)时,canvas 会脱离滚动流,浮动在固定位置,不会跟随页面内容一起滚动。
表现
- 页面滚动时,canvas 始终停留在屏幕的同一位置
- canvas 覆盖在其他内容之上,造成视觉错乱
解决方案:离屏渲染 + 图片展示
将 canvas 移到屏幕外(不可见区域),用 echarts 在 canvas 上渲染图表,然后通过 wx.canvasToTempFilePath 导出为 PNG 图片,最后用 <image> 组件展示。<image> 是普通组件,正常参与页面滚动。
<!-- chart.wxml -->
<view class="chart-container">
<canvas id="chart-canvas" type="2d" class="chart-canvas"></canvas>
<image wx:if="{{chartImage}}" src="{{chartImage}}" class="chart-image" mode="widthFix" />
</view>
/* chart.wxss */
.chart-container {
width: 100%;
position: relative;
}
.chart-canvas {
width: 100%;
height: 400rpx;
position: absolute;
left: -9999px; /* 移到屏幕外 */
top: 0;
}
.chart-image {
width: 100%;
display: block;
}
核心流程
flowchart TD
A[attached 生命周期] --> B[查找 canvas 节点]
B --> C[添加兼容方法
addEventListener / removeEventListener / dispatchEvent] D[options 数据到达] --> E[查询 canvas 尺寸] C --> E E --> F[设置缓冲区
canvas.width / canvas.height] F --> G[echarts.init] G --> H[setOption 渲染图表] H --> I[延迟导出图片
wx.canvasToTempFilePath] I --> J[image 组件展示
正常参与页面滚动]
addEventListener / removeEventListener / dispatchEvent] D[options 数据到达] --> E[查询 canvas 尺寸] C --> E E --> F[设置缓冲区
canvas.width / canvas.height] F --> G[echarts.init] G --> H[setOption 渲染图表] H --> I[延迟导出图片
wx.canvasToTempFilePath] I --> J[image 组件展示
正常参与页面滚动]
坑二:导出图片时 ECharts 动画未完成,导致图表渲染不完整
问题描述
采用上述离屏渲染方案后,柱状图的柱子高度明显偏矮(如实际值 1200 只渲染到约 900 的高度),折线图的数据点挤在一起。但坐标轴、刻度标签显示完全正确。第二次数据刷新后偶尔恢复正常。
排查过程(走过的弯路)
由于"坐标轴正确但数据系列偏小"这一现象极具迷惑性,排查方向一度偏离到:
- ❌ 怀疑 canvas 缓冲区 DPR 缩放不匹配 → 调整
canvas.width = width * dpr→ 无效 - ❌ 怀疑
echarts.init的devicePixelRatio参数处理异常 → 移除/调整 → 无效 - ❌ 怀疑 canvas 2D 渲染上下文被重置 → 避免重复
canvas.width赋值 → 无效 - ❌ 怀疑首次布局尺寸不稳定 → 添加延迟重建机制 → 部分场景有效但不稳定
- ❌ 怀疑
canvasToTempFilePath导出时缩放参数不对 → 调整各种 width/height → 无效
真正根因
ECharts 默认启用动画(animation: true)。
调用 setOption 后,柱子从 0 高度逐渐增长到目标高度,折线从起始位置逐渐展开到目标位置,动画默认持续约 1000ms。而 canvasToTempFilePath 在 setOption 后仅 300ms 就执行导出——此时动画尚未完成,柱子只画到了中间高度。
setOption 后立即绘制完成。
解决方案
在图表 options 中显式禁用动画:
// overview.js - _buildChartOptions()
return {
animation: false, // ← 关键!离屏导出方案必须禁用动画
grid: baseGrid,
xAxis: { ... },
yAxis: { ... },
series: [ ... ],
};
禁用后,setOption 立即完成完整渲染,300ms 后导出的图片包含完整的图表内容。
总结:离屏渲染方案的关键注意事项
| 要点 | 说明 |
|---|---|
| 禁用动画 | options 中必须包含 animation: false |
| canvas 缓冲区 | 使用逻辑像素(不乘 DPR),canvas.width = width |
| echarts.init | 不传 devicePixelRatio,避免额外缩放 |
| 导出图片 | canvasToTempFilePath 不指定 width/height 参数,使用缓冲区原始尺寸 |
| 数据刷新 | 仅调用 setOption(options, true),不销毁重建 echarts |
| 图表切换 | 才需要 dispose + 重新 init |
| canvas 兼容方法 | init 前需添加 addEventListener、removeEventListener、dispatchEvent 空实现 |
适用边界
- 以上问题仅影响离屏渲染 + 图片导出方案
- 如果 canvas 直接显示在页面上(不参与自定义滚动容器),则不受动画问题影响
- Web 环境不受影响(无 canvas 转图片步骤,无原生组件滚动问题)