问题描述
vue使用高德地图点标记,刚开始使用的是Marker,但是数目超过300,滑动就卡顿,按文档来说,Marker 类型推荐在数据量为 500 以内时使用,不应该卡顿。后边就开始对这个bug进行两天脑秃的探究了
1.换成 LabelMarker
既然Marker类型不行,看了文档, LabelMarker支持更多的点标记,那就换成 LabelMarker,然后自信满满的发布,结果还是不行。
2.与官方沟通寻求解决方案
按照文档写了代码,都不行,只能跟官方发工单沟通,我把项目中使用到的初始化代码贴给了官方,官方看完也说没问题,给了一个js的demo,运行了一下,确实不卡。demo代码贴一下
Labelmarker_Test.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<link rel="stylesheet" href="https://a.amap.com/jsapi_demos/static/demo-center/css/demo-center.css" />
<title>地图显示</title>
<style>
html,
body,
#container {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="container"></div>
<!-- 加载地图JSAPI脚本 -->
<script src="https://webapi.amap.com/maps?v=2.0&key=自己的key"></script>
<script src="/data.js"></script>
<script>
var map = new AMap.Map('container', {
viewMode: '2D', // 默认使用 2D 模式,如果希望使用带有俯仰角的 3D 模式,请设置 viewMode: '3D',
zoom: 11, //初始化地图层级
center: [116.397428, 39.90923] //初始化地图中心点
});
let markers = [];
for (var i = 0; i < data.length; i++) {
var marker;
let item = data[i];
// console.log("地图marker", item);
let imageUrl = 'https://a.amap.com/jsapi_demos/static/demo-center/marker/express2.png';
var icon = {
// 图标类型,现阶段只支持 image 类型
type: "image",
// 图片 url
image: imageUrl,
// 图片尺寸
size: [44, 50],
// 图片相对 position 的锚点,默认为 bottom-center
anchor: "center",
};
var text = {
// 要展示的文字内容
content: item.orgName,
// 文字方向,有 icon 时为围绕文字的方向,没有 icon 时,则为相对 position 的位置
direction: "top",
// 在 direction 基础上的偏移量
offset: [0, 0],
// 文字样式
style: {
// 字体大小
fontSize: 12,
// 字体颜色
fillColor: "#000000",
//backgroundColor: "#ffffff",
},
};
if (item.latitude && item.longitude) {
let positionV = [item.longitude, item.latitude];
marker = new AMap.LabelMarker({
name: "标注2", // 此属性非绘制文字内容,仅最为标识使用
position: positionV,
zIndex: i,
opacity: 1,
// 将第一步创建的 icon 对象传给 icon 属性
icon: icon,
// 将第二步创建的 text 对象传给 text 属性
text: text,
zooms: [3, 20],
allowCollision: false,
});
}
var onMarkerClick = function (e) {
console.log("marker 点击");
that.hospital = item;
that.sheetShow = true;
};
marker.on("click", onMarkerClick); //绑定click事件
markers.push(marker);
}
var labelsLayer = new AMap.LabelsLayer({
zooms: [3, 20],
zIndex: 1000,
// 该层内标注是否避让
collision: true,
// 设置 allowCollision:true,可以让标注避让用户的标注
allowCollision: true,
});
labelsLayer.add(markers);
map.add(labelsLayer);
</script>
</body>
</html>
data.js
const data = [
{ unifiedOrgCode: "320211PD3854", orgName: "门诊部", branchCode: null, branchName: null, orgLevel: null, orgGrade: null, provinceCode: null, cityCode: null, orgAddress: "太湖新城金融街11号", latitude: 31.488750, longitude: 120.304928, introduction: null, orgUrl: "https://www.wxhealth.net:8241/File/GetFile/76c0c8d6be03c2e1", reserveNote: null, orgTraffic: null, feature: null, ticketDetial: null, isReservation: 1, appointDays: 7, canAppointToday: 1, doctorAppointDays: null, showOrder: null, accessType: null, h5Info: null, distance: "0.10", contactPhone: null, characteristics: null, orgType: "clinic", districtCode: "3201", economicType: 2 }, { unifiedOrgCode: "320211PD78", orgName: "同仁大药房", branchCode: null, branchName: null, orgLevel: null, orgGrade: null, provinceCode: null, cityCode: null, orgAddress: "区观山路38号", latitude: 31.491185, longitude: 120.302667, introduction: null, orgUrl: "https://www.wxhealth.net:8241/be03c2e1", reserveNote: null, orgTraffic: null, feature: null, ticketDetial: null, isReservation: 1, appointDays: 7, canAppointToday: 1, doctorAppointDays: null, showOrder: null, accessType: null, h5Info: null, distance: "0.40", contactPhone: null, characteristics: null, orgType: "clinic", districtCode: "320211", economicType: 2 },
]
有非常多的数据,我这边只贴出来数据结构。如果要模拟数据,可以使用循环遍历,然后动态修改经纬度的办法,比如
demoData.forEach((element) => {
//TODO 增加多条数据,测试是否卡顿
let latTest = element.latitude;
let lonTest = element.longitude;
for (let i = 0; i < 50; i++) {
element.latitude = latTest + 0.00001 * i;
element.longitude = lonTest + 0.00001 * i;
mapData.push(element);
}
});
是否只有Vue会卡顿
我试了官方给的js demo,数据多的时候确实不卡,所以我怀疑是不是vue的问题,我试着不用Vue的初始化,在vue中使用js那种方式来初始化
在public下的index.html中的 <head>
里添加
<script src="https://webapi.amap.com/maps?v=2.0&key=自己的key"></script>
然后对应的Vue里使用
var map = new AMap.Map('container', {
viewMode: '2D', // 默认使用 2D 模式,如果希望使用带有俯仰角的 3D 模式,请设置 viewMode: '3D',
zoom: 11, //初始化地图层级
center: [116.397428, 39.90923] //初始化地图中心点
});
但是还是不行,还是会卡顿
治标不治本的办法
既然技术上不好实现,那修改需求呢,比如对于大量数据,我分N组,比如四组吧,然后监听地图缩放事件,在某一范围,只显示第一组,然后在另一范围,显示第二组,每组显示的数据比较少,就不会出现卡顿,但是这是治标不治本的办法,如果数据很多呢,每组超过几千,岂不是还是很卡,所以放弃
不经意发现的bug
只能技术上解决了,按照官方代码,一点一点的删项目中的代码,看看到底是哪一块代码引起的卡顿,经过各种尝试,最终锁定了一个变量,这个变量是data里的localMap,我在map初始化以后,将this.localMap=map。这个在逻辑上是没有问题的,所以我刚开始并没有在意,但是确实是这段代码引起的卡顿,所以我反馈给官方,
然后官方给的回复:
所有地图相关的实例不要放在 vue 的可响应数据中,响应数据会劫持属性,地图的属性会被修改,另外,劫持的属性可能和渲染有关,那么会增加很多响应的计算,会很卡;
懂了吧,就是不能在地图相关的实例里,进行任何data里字段的赋值,否则就会很卡
怎样获得地图实例
我现在的需求是,我地图控件的上边,加了一个图标,用来设置居中位置,使用了setCenter方法,这个需要用到当前地图的对象,所以我想使用data里的字段与map关联,但是这样会卡顿,有什么办法拿到map的对象,在外层进行操作????
既然不能在地图实例代码里给外层字段赋值,想到的第一个办法就是把map对象return出去,然后用字段接收,但是呢,还是不行,再一个办法,就是通过方法,传入map对象赋值,还是不行,所以,继续与官方沟通,得到的回复是
地图加载后,把AMap挂载到window上面,后面直接从window中获取即可。
然后怎么才能获取到呢,试了几种办法,也没找到怎么获取AMap的setCenter方法,最后百度了一下,可以这样得到实例,将map与window关联,window.map=new AMap.Map(“container”, {…});然后在外层通过window.map对地图操作
window.map.setCenter([120.318321, 31.497963]);
initAMap() {
AMapLoader.load({
key: "自己的key", //设置您的key
version: "2.0",
plugins: ["AMap.ToolBar", "AMap.Driving"],
AMapUI: {
version: "1.1",
plugins: [],
},
Loca: {
version: "2.0",
},
})
.then((AMap) => {
window.map = new AMap.Map("container", {
viewMode: "3D",
zoom: 10,
zooms: [2, 22],
center: [120.318791, 31.497993],
});
let markers = [];
let demoData = dataInJs();
let mapData = [];
demoData.forEach((element) => {
// //TODO 增加多条数据,测试是否卡顿,正式发布删除
let latTest = element.latitude;
let lonTest = element.longitude;
for (let i = 0; i < 10; i++) {
element.latitude = latTest + 0.00001 * i;
element.longitude = lonTest + 0.00001 * i;
mapData.push(element);
}
});
console.log("mapData", mapData);
for (var i = 0; i < mapData.length; i++) {
var marker;
let item = mapData[i];
let imageUrl =
"https://a.amap.com/jsapi_demos/static/demo-center/marker/express2.png";
var icon = {
// 图标类型,现阶段只支持 image 类型
type: "image",
// 图片 url
image: imageUrl,
// 图片尺寸
size: [44, 50],
// 图片相对 position 的锚点,默认为 bottom-center
anchor: "center",
};
var text = {
// 要展示的文字内容
content: item.orgName,
// 文字方向,有 icon 时为围绕文字的方向,没有 icon 时,则为相对 position 的位置
direction: "top",
// 在 direction 基础上的偏移量
offset: [0, 0],
// 文字样式
style: {
// 字体大小
fontSize: 12,
// 字体颜色
fillColor: "#000000",
},
};
if (item.latitude && item.longitude) {
let positionV = [item.longitude, item.latitude];
marker = new AMap.LabelMarker({
name: "标注2", // 此属性非绘制文字内容,仅最为标识使用
position: positionV,
zIndex: i,
opacity: 1,
// 将第一步创建的 icon 对象传给 icon 属性
icon: icon,
// 将第二步创建的 text 对象传给 text 属性
text: text,
zooms: [3, 20],
allowCollision: false,
});
}
var onMarkerClick = function (ee) {
console.log("marker 点击" + ee);
};
marker.on("click", onMarkerClick); //绑定click事件
markers.push(marker);
}
var labelsLayer = new AMap.LabelsLayer({
zooms: [3, 20],
zIndex: 1000,
// 该层内标注是否避让
collision: true,
// 设置 allowCollision:true,可以让标注避让用户的标注
allowCollision: true,
});
labelsLayer.add(markers);
window.map.add(labelsLayer);
// this.setLocalMap(map);
setTimeout(() => {
console.log("定位");
this.testMap();
}, 1000);
})
.catch((e) => {
console.log(e);
});
//return map;
},
总结
标注就使用官方文档上的就可以了,如果还是卡顿,检查一下是否在map实例化的时候,有对data里字段的操作,比如this.localMap=map之类的。如果想要获取到map的实例,用于外层对地图的操作,可以给window.map,然后获取,将map实例与window关联,这样不会卡顿