问题:
公司的项目需要在地图上显示上万个覆盖物(实际最多会有两万多个,后面提到的聚合点加载时间都以两万个点为准)。
其实在正常生成聚合点的过程并不会很慢(500ms左右),但是在聚合点生成后,用户如果操作地图(缩放、移动等),就会出现卡顿现象,这也是一开始发现的问题。在后续的优化过程中还发现另外两个问题,如果在加载聚合点之前,用户对地图进行过操作,加载聚合点就会变得非常缓慢(1000ms以上),同时,每次删除聚合点在重新添加都会比上一次慢
解决方案:
直接说解决方案,把项目中引入的MarkerClusterer文件替换成官方github最新的那个就行,文件地址。
实测首次加载仅需100ms左右,且没有上面提到的其它问题出现,同时,如果屏幕可见范围内的点位较少时,加载时间也会相应缩短。
本来在后续优化过程中发现另外两个问题后,就觉得应该从源码入手解决,结果上github clone了源码,引入后发现就没问题了。原先引入的是这个代码,这个是旧版本的代码,看了一下git记录,其实2017/12/4就已经有优化了,但是有些老项目可能还在使用旧版本的MarkerClusterer。甚至是新项目在cv代码时也不小心引入了旧版本的MarkerClusterer。
2024/08/26 - 补充(好久没登账号了,刚看到一条评论,所以想补充一些东西)
新版本的聚合点代码,虽说是做了优化。但是并不是说就一定不会出现卡顿了,造成卡顿的原因是很多的,我自己主要遇到的问题,就是覆盖物导致的卡顿,但是地图上不会只有“聚合点”这一种覆盖物。
说下这次文章更新前,没有提到的两种卡顿,一个是在地图上拥有大量覆盖物,然后操作地图时,一个是在同时添加大量覆盖物时。(以下内容围绕这两个问题讨论)
操作地图时的卡顿
操作地图时的卡顿,我认为是百度地图需要实时计算各个覆盖物的位置,此时如果覆盖物过多,计算量较大,就会阻塞界面的渲染,从而导致卡顿。既然是计算覆盖物位置造成的,那就不让它计算。
我的方案是,监听百度地图的moving,moveend,zoomstart,zoomend事件,在moving和zoomstart时,去掉地图上的所有覆盖物,然后在moveend和zoomend时,将用户可见的部分重新添加到地图上。
百度地图提供了getBounds方法(在百度地图实例上调用),返回一个Bounds实例,可获取地图当前的可见范围。调用Bounds实例的containsPoint,可判断一个Point是否在Bounds的范围内
/**
* @param point {BMap.Point}
*/
function isVisible(point){
const bounds = bMapInstance.getBounds()
return bounds.containsPoint(point)
}
如果是由多个点组成的覆盖物,可调用Bounds实例上的intersects方法,来判断两个Bounds是否有交集,以确定覆盖物是否可见
/**
* @param bounds {BMap.Bounds}
* @return {boolean}
*/
function isVisible(bounds) {
const bMapBounds = bMapInstance.getBounds()
return !!bMapBounds.intersects(bounds)
}
几个注意点:
一个是监听地图移动的事件,最好用moving不要用movestart,在线上测试时,发现了一个问题,在用户点击地图时会触发地图的movestart事件,但是之后没有触发moveend,这就会导致功能异常(我个人认为是线上浏览器版本太老,所以会有一些兼容性的问题)。
还有一个是要做防抖,在moveend和zoomend触发时,不代表地图的操作就已经停止了,可能短时间内还会再次触发moving和zoomstart,所以必须有一些防抖的处理
export function addStartAndEndEventToBMap(bMapInstance, start, end) {
let doing = false
function _start() {
if (doing) return
doing = true
start()
}
function _end() {
if (!doing) return
doing = false
setTimeout(() => !doing && end(), 200)
}
bMapInstance.addEventListener("zoomstart", () => {
_start()
})
bMapInstance.addEventListener("moving", () => {
_start()
})
bMapInstance.addEventListener("moveend", () => {
_end()
})
bMapInstance.addEventListener("zoomend", () => {
_end()
})
}
同时添加大量覆盖物时的卡顿
这个应该就很好理解了,大量的代码执行势必会阻塞页面的渲染。我的解决方案是,在覆盖物过多时,将覆盖物分批次添加到地图上。保证每一批覆盖物的添加都不会造成明显的卡顿,同时在每一批中间用定时器隔开一段时间,这一段时间可以用来接收用户的交互以及进行页面的渲染。这样的方案会导致覆盖物的添加变成异步的,同时也会耗费更多的时间,但是可以解决卡顿问题。