Ceiling

微信小程序开发避坑 - Echarts雷区

背景

在微信小程序中使用 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 组件展示
正常参与页面滚动]

坑二:导出图片时 ECharts 动画未完成,导致图表渲染不完整

问题描述

采用上述离屏渲染方案后,柱状图的柱子高度明显偏矮(如实际值 1200 只渲染到约 900 的高度),折线图的数据点挤在一起。但坐标轴、刻度标签显示完全正确。第二次数据刷新后偶尔恢复正常。

排查过程(走过的弯路)

由于"坐标轴正确但数据系列偏小"这一现象极具迷惑性,排查方向一度偏离到:

  1. ❌ 怀疑 canvas 缓冲区 DPR 缩放不匹配 → 调整 canvas.width = width * dpr → 无效
  2. ❌ 怀疑 echarts.initdevicePixelRatio 参数处理异常 → 移除/调整 → 无效
  3. ❌ 怀疑 canvas 2D 渲染上下文被重置 → 避免重复 canvas.width 赋值 → 无效
  4. ❌ 怀疑首次布局尺寸不稳定 → 添加延迟重建机制 → 部分场景有效但不稳定
  5. ❌ 怀疑 canvasToTempFilePath 导出时缩放参数不对 → 调整各种 width/height → 无效

真正根因

ECharts 默认启用动画(animation: true)。

调用 setOption 后,柱子从 0 高度逐渐增长到目标高度,折线从起始位置逐渐展开到目标位置,动画默认持续约 1000ms。而 canvasToTempFilePathsetOption 后仅 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 前需添加 addEventListenerremoveEventListenerdispatchEvent 空实现

适用边界

  • 以上问题仅影响离屏渲染 + 图片导出方案
  • 如果 canvas 直接显示在页面上(不参与自定义滚动容器),则不受动画问题影响
  • Web 环境不受影响(无 canvas 转图片步骤,无原生组件滚动问题)