微信小程序的map组件默认提供了marker、polyline等基础覆盖物,但在需要做复杂矢量图形叠加、动态区域高亮或业务数据可视化时,这些原生元素在样式与交互上都有局限。此时可以利用map组件的custom-layer(自定义图层)能力,将开发者自己绘制的画面作为独立图层覆盖在底图上方,实现完全可控的矢量渲染。

一、自定义图层与canvas矢量绘制的基本原理
map组件的自定义图层本质上是一个脱离了普通页面文档流的绘图表面。开发者通过获取map上下文并绑定一个canvas节点,使canvas的输出内容能够按地图投影关系进行同步平移与缩放。底层逻辑是:地图引擎在渲染时将自定义图层当作纹理处理,每一帧都会根据地图中心点、缩放级别计算图层的仿射变换矩阵,从而保证canvas里的矢量图形与底图地理坐标对齐。
在微信小程序中,推荐使用新版canvas 2d接口而非旧的canvas context。旧接口性能较差且不支持部分硬件加速特性。通过wx.createSelectorQuery()拿到canvas节点后,使用canvas.getContext('2d')得到绘制上下文,就可以像在网页中一样调用moveTo、lineTo、fill等方法来画矢量图形。需要注意的是,自定义图层中的canvas并不是直接显示在页面上,而是被地图组件捕获,因此不要给这个canvas设置过大的尺寸,一般与屏幕分辨率匹配即可。
坐标映射是核心难点。地图使用经纬度坐标,而canvas使用像素坐标。我们需要先把业务中的经纬度点通过map的getRegion或自己维护的缩放比例尺转换为canvas内的x、y值。常见做法是监听map的regionchange事件,在回调中读取当前视野中心的经纬度与缩放等级,重算所有矢量点的像素位置并触发重绘。只有做好了这一步,图形才不会在用户拖动地图时“飘移”。
二、从零实现多边形与折线的绘制工具
一个实用的矢量图形工具应当支持动态添加顶点、预览图形以及清除重画。下面以绘制一个多边形区域为例,展示核心代码。我们先在wxml中放置map并开启自定义图层,再放置一个不可见的canvas供绘制使用。
以下代码演示了如何根据经纬度数组在canvas上画出闭合多边形。代码中project函数负责经纬度到像素的转换,实际项目里可替换为微信地图官方提供的mapContext辅助方法或自写墨卡托换算。
// 假设已获取canvas节点与2d上下文
const canvas = document.getElementById('layer');
const ctx = canvas.getContext('2d');
// 将经纬度转换为canvas像素坐标
function project(lng, lat, mapState) {
const x = (lng - mapState.left) / (mapState.right - mapState.left) * canvas.width;
const y = (mapState.top - lat) / (mapState.top - mapState.bottom) * canvas.height;
return { x, y };
}
// 绘制多边形
function drawPolygon(points, mapState) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
points.forEach((p, i) => {
const pos = project(p.lng, p.lat, mapState);
if (i === 0) {
ctx.moveTo(pos.x, pos.y);
} else {
ctx.lineTo(pos.x, pos.y);
}
});
ctx.closePath();
ctx.fillStyle = 'rgba(0, 128, 255, 0.3)';
ctx.fill();
ctx.strokeStyle = '#0080ff';
ctx.lineWidth = 2;
ctx.stroke();
}
上述逻辑可以直接封装为一个绘图模块。当用户在小程序界面点击“添加顶点”时,把点击处的经纬度推入数组并调用drawPolygon即可看到图形延展。若是折线,只需去掉closePath与填充逻辑,保留描边就能形成路径线。工具层还应当暴露undo与reset方法,方便编辑。
为了提升体验,可以在canvas上叠加一个透明的高优先级触摸层来捕获绘图点击,避免事件被map吞噬。小程序里通过catchtouchstart绑定手势,将屏幕坐标反查为经纬度后再交给绘制函数。这样形成的“开发工具”既能用于后台录入地理围栏,也能用于前端用户自行圈选配送范围。
三、性能优化与常见错误排查
自定义图层最容易被忽视的问题是重绘频率。部分开发者在regionchange的每次回调中都无条件清空并重画全部图形,当地图快速滑动时会造成明显卡顿。正确方式是对地图视野做节流:只有在缩放等级变化超过阈值或中心移动超出某个像素容差时才重算坐标,否则直接复用上一次的变换矩阵做canvas整体偏移,交给地图引擎处理。
另一个典型错误是图形错位。这通常源于canvas尺寸与map组件逻辑像素不一致。微信小程序中wx.getSystemInfoSync().pixelRatio会影响实际绘制缓冲大小,若未将canvas的width与height乘以像素比,而在绘制时又用了css像素坐标,图形就会整体偏移。建议统一在初始化时设定canvas.width = clientWidth * dpr,并在project函数里也乘上dpr。
对比svg与canvas两种矢量方案,svg在微信小程序自定义图层中并不被原生支持,因为图层接收的是位图纹理;若强行用svg转图片再贴图,动态编辑时代价极高。canvas方案虽然需要手动管理重绘,但能利用gpu合成,在绘制上百个多边形时仍保持流畅。因此开发工具应以canvas为基座,仅在导出数据时将其转成geojson等矢量描述供后端存储。
| 方案 | 动态编辑 | 性能表现 | 适用场景 |
|---|---|---|---|
| 原生polyline | 弱 | 好 | 简单路径展示 |
| custom-layer+canvas | 强 | 中高 | 复杂矢量工具 |
| svg转图 | 差 | 低 | 静态装饰 |
当工具开发完成后,还应考虑多端兼容。微信开发者工具与真机在自定义图层刷新时机上略有差异,真机要求在onReady之后至少一次mapContext.updateCustomLayer调用才会激活图层。若遇到图层空白,优先检查是否漏了首帧主动更新,以及canvas是否已被回收。把以上要点纳入工具基类,就能稳定支撑各类矢量绘图需求。