微信小程序的map组件本身并不直接提供热力图接口,但通过腾讯位置服务提供的个性化地图能力,开发者可以在底图之上叠加自定义图层,其中就包括热力图层。很多做选址分析、订单监控、人群分布展示的小程序都会用到这个能力。热力图的实现思路其实分两类:一类是密度热力图,只看点的疏密程度;另一类是权重热力图,每个点带一个数值,最终颜色深浅由密度和权重共同决定。这两种方式的参数配置差异不小,下面分别展开说明。

一、热力图的实现前提与图层初始化
在小程序里做热力图,第一步不是写数据,而是确认底图配置。map组件要显示自定义热力图层,必须使用腾讯位置服务的个性化地图,也就是说你需要先在腾讯位置服务后台创建一个样式,拿到对应的subkey。没有这个subkey,后面所有图层配置都不会生效,这是最常见的第一个坑。
拿到subkey之后,在wxml中声明map组件时通过subkey属性传入,同时开启enable-satellite或普通矢量图都可以,热力图层在两种底图上都能叠加。需要强调的是,subkey属性只能写在小程序的map组件上,不能通过JavaScript动态注入,所以要在开发阶段就确定好。
<map id="heatMap" latitude="39.908823" longitude="116.397470" scale="12" subkey="你的个性化地图key" style="width:100%;height:100vh" ></map>
初始化图层时,需要通过MapContext获取地图上下文,再调用initHeatMap相关能力。注意这个方法必须在地图实例创建完成之后调用,建议放在onReady或延迟执行,否则会报找不到图层的错误。
二、密度热力图的参数配置
密度热力图是最简单的形态,只需要提供点位经纬度,系统根据单位面积内点的数量自动计算颜色梯度。它的核心参数有三个:radius控制单个点的影响半径,单位是像素;opacity控制整体透明度;gradient自定义颜色梯度。radius值越大,热力图越显得“糊”,越小则越接近散点,一般建议在20到40像素之间根据地图缩放级别调整。
const points = [
{ latitude: 39.908, longitude: 116.397 },
{ latitude: 39.912, longitude: 116.401 },
{ latitude: 39.905, longitude: 116.390 }
];
this.mapCtx.initHeatMap({
type: 'density', // 密度热力图
data: points,
radius: 30, // 影响半径,像素
opacity: 0.7, // 整体透明度
gradient: {
0.2: '#50a3ba',
0.4: '#64b76a',
0.6: '#f7d94c',
0.8: '#f29e4c',
1.0: '#e24c4c'
}
});gradient对象的键是0到1之间的归一化位置,值是对应颜色。键必须从小到大排列且起始值不要设为0以外的数,否则部分机型会出现颜色断层。另外,颜色值建议使用六位十六进制写法,八位的RGBA写法在部分安卓机型上解析异常。
密度热力图适合的场景是“只关心多少,不关心大小”,比如共享单车停放点分布、打卡签到分布。如果你的数据里每个点还有一个业务数值,比如订单金额、门店销量,那就应该用权重热力图。
三、权重热力图的配置与归一化处理
权重热力图在数据结构上多了一个weight字段,代表该点的权重值。配置时通过type: 'weighted'声明,并需要额外指定weightKey或直接在数据对象中写入weight属性。权重值会被叠加到密度计算中,两个相同位置但权重不同的点,颜色深浅会有明显差异。
const weightedPoints = [
{ latitude: 39.908, longitude: 116.397, weight: 35 },
{ latitude: 39.912, longitude: 116.401, weight: 120 },
{ latitude: 39.905, longitude: 116.390, weight: 8 }
];
this.mapCtx.initHeatMap({
type: 'weighted',
data: weightedPoints,
radius: 35,
weightKey: 'weight',
opacity: 0.8,
gradient: {
0.3: '#2c7bb6',
0.6: '#f4a582',
1.0: '#d7191c'
}
});这里有个非常关键的细节:weight字段是否需要归一化,取决于底层渲染逻辑。如果传入的权重值相差几个数量级,比如最大的1200、最小的3,直接渲染会导致小权重点几乎不可见。因此建议在前端先做一次线性归一化,把所有权重压到1到100之间:
// 对权重做线性归一化,避免量纲差异过大
const weights = weightedPoints.map(p => p.weight);
const max = Math.max(...weights);
const min = Math.min(...weights);
const normalized = weightedPoints.map(p => ({
...p,
weight: Math.round(((p.weight - min) / (max - min)) * 99) + 1
}));归一化之后再渲染,热力图的层次感会明显提升。如果业务上极端高值本身就是重点(比如爆单区域),也可以改用对数归一化,用Math.log处理后再压缩区间,避免头部数据把整体对比度吃掉。
四、动态更新与性能注意事项
热力图数据通常来自接口,且会随时间变化。更新数据时不要重复调用initHeatMap重建图层,正确做法是调用更新方法只刷新数据部分,重建图层在低端机上会带来肉眼可见的卡顿,尤其是点位超过两千个的时候。
点位数量方面,小程序端渲染热力图建议控制在五千个点以内。如果数据源动辄上万条,应该先在服务端做网格聚合,把相邻的点位合并成一个带聚合权重的点再下发,既减少传输量,也让热力形态更平滑。
最后提醒两点:一是radius的值不会随地图缩放自动适配,缩放级别变化大时需要监听regionchange事件动态调整半径;二是调试时如果热力图完全不显示,优先检查subkey是否绑定了对应的图层权限,其次是确认经纬度顺序没有写反,latitude在前longitude在后,写反了点位会落到海里,热力图自然一片空白。