UniApp 微信小程序 Map 组件自定义标记自定义marker气泡实现方案
·
微信小程序地图自定义标记:从 Web 到小程序的踩坑实录
一、需求

地图上显示带时间信息的打卡点:
- 起点:开图标 + 09:12 + 地点名称
- 终点:关图标 + 18:30 + 地点名称
二、Web 端怎么做
高德/百度地图 JS API 支持直接传 HTML:
const marker = new AMap.Marker({
content: `
<div class="bubble">
<img src="/icon/start.svg"/>
<span>09:12 起点位置</span>
</div>
`,
position: [116.397428, 39.90923],
offset: new AMap.Pixel(-20, -30)
});
CSS 随便写,响应式、动画、交互都能做。完事。
三、小程序为什么做不了
微信小程序的 Map 组件:
const marker = {
id: 1,
latitude: 30.5928,
longitude: 114.3055,
iconPath: '/static/marker.png', // ← 只能传图片路径!
width: 40,
height: 40
};
没有 content 属性,不支持 HTML,只认图片。
小程序运行环境是 JSCore,不是浏览器:
- 没有 DOM
- 没有 CSS 渲染引擎
- 无法对元素截图
四、尝试过的方案
方案 1:预生成所有图片(放弃)
想法:让设计师把各种时间组合都出图。
问题:
- 时间是动态的,无法穷举
- 地点名称也是动态的
- 包体积爆炸
结论:静态方案不可行。
方案 2:html2canvas(失败)
想法:Web 端常用 html2canvas 把 DOM 转图片,小程序应该也行。
代码:
import html2canvas from 'html2canvas';
const canvas = await html2canvas(document.getElementById('bubble'));
报错:
document is not defined
失败原因:小程序没有 document 对象。
方案 3:小程序原生组件 + 截图(失败)
想法:用 cover-view 渲染气泡,然后用小程序 API 截图。
代码:
<cover-view id="bubble" style="position:fixed;left:-9999px">
<cover-image src="/icon.svg"/>
<cover-view>09:12</cover-view>
</cover-view>
问题:
- 小程序没有通用的 DOM 截图 API
canvasToTempFilePath只能导出 Canvas,不能导出任意组件left: -9999px的元素获取不到尺寸
失败原因:小程序没有截图能力。
方案 4:base64 图片(失败)
想法:把 SVG 转成 base64,直接作为 iconPath。
代码:
const marker = {
iconPath: 'data:image/svg+xml;base64,PHN2Zy...'
};
报错:
iconPath 不支持 base64 格式
失败原因:小程序 Map 只支持本地路径或网络 URL。
五、最终方案:Canvas 绘制
唯一可行的路:用 Canvas 2D API 把气泡画出来,导出临时图片。
流程:
配置(文字、图标)→ Canvas 绘制 → 导出临时文件 → 设置 marker
核心代码:
// 1. 获取 Canvas(必须在组件实例上下文中)
const query = uni.createSelectorQuery().in(instance.proxy);
const { node: canvas } = await new Promise(r =>
query.select('#bubble-canvas').fields({ node: true }, r).exec()
);
const ctx = canvas.getContext('2d');
// 2. 计算尺寸
const textWidth = ctx.measureText('09:12 起点').width;
const width = padding + iconSize + gap + textWidth + padding;
// 3. 绘制
ctx.fillStyle = '#FFF';
roundRect(ctx, 0, 0, width, height, 30); // 圆角背景
ctx.fill();
const icon = await loadImage('/static/icon.svg', canvas);
ctx.drawImage(icon, padding, y, iconSize, iconSize);
ctx.fillStyle = '#1A1A1A';
ctx.fillText('09:12 起点', textX, height / 2);
// 4. 导出
const { tempFilePath } = await uni.canvasToTempFilePath({ canvas });
思想是这个就行了,具体怎么用canvas绘制可以用ai写工具函数,这里有些函数篇幅太长,就没有截图
使用:
<BubbleRenderer
text="09:12 起点位置"
icon-path="/static/icon/map_tags_20.svg"
@rendered="(path, w, h) => addMarker(path, w, h)"
/>
六、踩坑记录
| 坑 | 现象 | 解决 |
|---|---|---|
| Canvas 获取不到 | node 为 undefined | .in(instance.proxy) 指定组件上下文 |
roundRect 报错 | API 不支持 | 用 arcTo 手动画圆角 |
| marker 宽度无效 | [object Object] | Math.round(Number(width)) 转整数 |
| SVG 加载失败 | 图标不显示 | 加超时处理,失败画占位圆 |
七、总结
Web 端:HTML + CSS → 直接渲染
小程序:Canvas API → 手动绘制 → 导出图片
小程序没有 DOM,地图只认图片,所以必须用 Canvas 把"组件"画成"图片"。
更多推荐
所有评论(0)