微信小程序map组件渲染几百上千个marker,页面卡顿,如何使用点聚合解决?

1,062 阅读5分钟

问题回顾

几个月前,曾写过一篇文章,微信小程序map组件渲染几百个marker后,页面卡顿,如何解决,当时由于需求紧急,没有充足时间研究和查阅文档,当时想到的一种解决方案,原理是这样的:

控制地图可见区域大小,只获取地图当前区域范围内的maker,操作移动地图重新请求并加载maker。

现在暂时闲下来了,就想再好好研究下这个问题,在上一篇文章中,我曾贴了这张图 image.png 我们可以看到,在当前视图下,附近点位很多的时候,会出现一些数字点位。其实这种效果就是点聚合,简单来讲,就是将规定区域内,无法同时展示的点位通过数量点位展示在地图中,不必将所有点位都渲染展示出来。如此一来,使用了点聚合,不是就能解决点位过多,页面卡顿的问题了。

遥记得前些时候好像见过点聚合功能的介绍,好像在查阅微信小程序文档的时候也提到了点聚合。今天就好好阅读了下 微信地图map/ uni-app地图map 的文档,还真有,只怪当时了解太少,太着急,没认真研究文档啦。

1752569078425.png

1752570074807.png

关于点聚合,官方还给了一个示例代码。然后我就参照着示例代码,准备在我们的项目中试一试。完成后,感觉还不错。下图为我在平遥一码游中将其改为点聚合后的效果截图。

c9ee5a61b3b03d302b7992b01562af9.jpg

这里先说明一下,我发现在开发者工具中查看,体验效果不佳,直接点击聚点数字没反应,需要双击才能实现分离,分离效果并不理想,但在真机上只需轻轻点一下即可,不建议在微信开发者工具中体验和测试。

点聚合功能实现关键代码:

<template>
    <map 
        id="mapId" 
        ref="mapRef" 
        :polyline="polyline" 
        :latitude="latitude" 
        :longitude="longitude" 
        :scale="scale" 
        :min-scale="minScale" 
        :max-scale="maxScale" 
        :show-location="showLocation"
        @markertap="markertap" 
        @labeltap="markertap" 
        @regionchange="onMapRegionchange"
    >
        <cover-view slot="callout">
            <cover-view class="c-callout" v-for="(item, i) in markers" :marker-id="i" :key="i">
                {{ i + 1 }}
            </cover-view>
        </cover-view>
    </map>
</template>

<script>
export default {
    data: {
        mapCtx : null, // 创建地图上下文对象。
        markers : [], // 地图聚合点位数据
        defaultTheme : true,  // 是否使用默认的聚合样式
        appItem : {
            latitude: 37.211836,
            longitude: 112.180908,
        },
        // 地图默认坐标
        localLatitude: 37.202604,
        localLongitude: 112.184751,
    },
    // 在 `onLoad` 生命周期钩子中调用 `uni.createMapContext` 时,地图组件可能还未完全渲染完成,这会导致 `mapCtx` 无法正确获取到地图上下文
    //onLoad(){
        //this.initMap()
        //this.getMakersRequest()
    //},
    onReady() {
        this.initMap()
        this.getMakersRequest()
    },
    methods: {
        initMap(){
            //this.mapCtx = wx.createMapContext('mapId'); 
            this.mapCtx = uni.createMapContext("mapId", this);
            // 对聚合点进行初始化配置
            this.mapCtx.initMarkerCluster({
              enableDefaultStyle: this.defaultTheme, // 启用默认的聚合样式
              zoomOnClick: true, // 点击已经聚合的标记点时是否实现聚合分离
              gridSize: 60, // 聚合算法的可聚合距离,即距离小于该值的点会聚合至一起,以像素为单位
              complete(res) {
                console.log('initMarkerCluster', res)
              }
            })
            
            !this.defaultTheme && this.customClusterTheme();
        },
        customClusterTheme(){
            // initMarkerCluster 中参数 enableDefaultStyle 为 true 时不会触发下方 markerClusterCreate事件
            // 如果需要自定义点聚合数字框UI的可以在这里书写逻辑,个人觉得如果UI没有特殊要求,默认的已经挺好看了
            this.mapCtx.on('markerClusterCreate', res => {
              console.log('clusterCreate', res)
              const clusters = res.clusters
              const markers = clusters.map(cluster => {
                const {
                  center,
                  clusterId,
                  markerIds
                } = cluster
                return  {
                  ...center,
                  width: 0,
                  height: 0,
                  clusterId, // 必须
                  label: {
                    content: markerIds.length + '',
                    fontSize: 20,
                    width: 60,
                    height: 60,
                    bgColor: '#00ff00',
                    borderRadius: 30,
                    textAlign: 'center',
                    anchorX: 0,
                    anchorY: -30,
                  }
                }
              })
              this.mapCtx.addMarkers({
                markers,
                clear: false,
                complete(res) {
                  console.log('clusterCreate addMarkers', res)
                }
              })
            }) 
        },
        // // 清空所有地图点位
        //removeMarkers() {
            //this.mapCtx.addMarkers({
              //clear: true, // 是否先清空地图上所有 marker
              //markers: [] // 同传入 map 组件的 marker 属性,为空相当于清空现有map中的所有marker点
            //})
        //},
        // 获取地图点位
        getMakersRequest(){
            this.markers = []
            this.$http.post("/xxxx/xxxx", {
            }).then(({ data }) => {
                this.markers = data.records.map((item, index) => {
                    return {
                        // 保留自定义参数, 用于查询或其他操作使用
                        ...item,
                        //uid: gId,
                        //dataId: item.id,
                        //type: 'spot',
                        // 这里的joinCluster非常关键,它是点是否能正常聚合的关键一步
                        joinCluster : true,  // 是否参与点聚合,默认false不参与点聚合
                        iconPath: `${this.$imgBaseUrl}/tour/icon-flag.png`, // 显示的图标
                        id: item.id, // marker 点击事件回调会返回此 id
                        latitude: item.latitude, // 纬度
                        longitude: item.longitude, // 经度
                        width: 25, // 标注图标宽度
                        height: 28, // 标注图标高
                        // 为标记点旁边增加标签
                        label: {
                            content: item.name,
                            borderRadius: 36,
                            borderWidth: 1,
                            borderColor: '#999',
                            bgColor: '#fff',
                            display: 'ALWAYS',
                            textAlign: 'center',
                            padding: 5
                        },
                        // 标记点上方的气泡窗口
                        callout: {
                            content: item.name,
                            padding: 5,
                            display: 'BYCLICK' // 'BYCLICK':点击显示; 'ALWAYS':常显
                        }
                    }
                })
                
                // // 定位到指定位置
                //this.goToCenter({
                    //latitude: this.appItem.latitude,
                    //longitude: this.appItem.longitude
                //})
                
                // 将参与聚合的点位添加到地图上下文对象中
                this.mapCtx.addMarkers({
                    markers : this.markers, // 添加请求到的makers到地图中
                    clear: true, // 设置为true,先清空地图上所有 marker
                }) 
            })
        },
        
        // // 设置中心点经纬度
        //setCenter(latitude = '', longitude = '') {
            //this.latitude = latitude || this.localLatitude;
            //this.longitude = longitude || this.localLongitude;
        //},
        // // 定位到指定位置
        //goToCenter(location) {
            //this.showLocation = true
            //this.setCenter(location.latitude,location.longitude)
        //}, 

    }
}
</script>

注意事项:

一、未改为点聚合功能前,地图中的点markers是响应式获取的,

<map id="mapId" ref="mapRef" :markers="markers"></map>

修改为点聚合后,在map的html结构中去掉了:markers="markers",

<map id="mapId" ref="mapRef"></map>

markers数据通过 MapContext.addMarkers 动态加入,

// 将参与聚合的点位添加到地图上下文对象中
this.mapCtx.addMarkers({
    markers : this.markers, // 添加请求到的makers到地图中
    clear: true, // 设置为true,先清空地图上所有 marker
}) 

个人经过验证,只能在map的html中去掉:markers="markers",然后通过 MapContext.addMarkers 动态添加点位才能实现点聚合,否则即便其他几个步骤都做了,书写无误,同样无法实现。

二、为了一个好的点聚合体验,地图的缩放比例scale需要我们根据实际情形设置一个合适的值,避免太大,数据多,达不到解决卡顿的问题,太小,就几个数字点位在地图上。在使用点聚合后,如无特殊情形,一般情况下,个人觉得,最大、最小缩放级别都可以不用设置了,直接使用默认值即可。

使用点聚合之前:

<map 
    id="mapId" 
    ref="mapRef" 
    :markers="markers"
    :polyline="polyline" 
    :latitude="latitude" 
    :longitude="longitude" 
    :scale="scale" 
    :min-scale="minScale" 
    :max-scale="maxScale" 
    :show-location="showLocation"
    @markertap="markertap" 
    @labeltap="markertap" 
    @regionchange="onMapRegionchange"
>
</map>

使用点聚合之后:

<map 
    id="mapId" 
    ref="mapRef" 
    :polyline="polyline" 
    :latitude="latitude" 
    :longitude="longitude" 
    :scale="scale" 
    :show-location="showLocation"
    @markertap="markertap" 
    @labeltap="markertap" 
    @regionchange="onMapRegionchange"
>
</map>

如果不是在小程序中,地图使用点聚合功能就更不用担心了,毕竟小程序原生map加入聚合功能可是晚于web和app端,大家有兴趣或者需要用到的可以自己去查文档。比如在H5中使用腾讯地图点聚合功能,腾讯地图点聚合,哎呀,啰嗦的老毛病又犯了,今天就这样吧,很感谢您还能看到这里。