vue 将百度地图或者高德地图组件化
一、前言
百度地图已经有了 react 相关的组件库,本人用的百度地图 v3.0 和 vue3
我仅仅是抛砖引玉,百度地图 webgl、高德地图都是一样的,因为底层都是通过 js 控制地图
如果用组件的方式开发,比如我将 BMap.Marker
作为一个组件,我暴露一个参数position
,其目的是控制 BMap.Marker
位置
那么只要我修改position
,BMap.Marker
在地图中的就会自动改变
和用js相比,无疑会减少很多步骤,方便省心(同理,其实用vue开发,比用原生html、js 更快!),以下是我自己在使用的代码截图
二、实现
这里我主要讲讲 BMap、BMap.Marker、自定义 Overlay
其实其他的覆盖物,相信只要你看了 BMap.Marker,就知道如何生成了,原理一样的
因为我只写了我自己需要的,有很多没用到的操作,我懒就没写了
核心其实就一个,如何给子组件注入已经生成的地图对象,就没了!
2.1 BMap实现
注意控制地图的生命周期,以及如何暴露 map 元素给子组件
<template>
<div class="c-BMap" ref="targetDom"></div>
<!-- 等地图挂载后,再加载子组件 -->
<slot v-if="isMapMount"></slot>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, provide, ref, shallowRef, watch } from 'vue';
const props = withDefaults(defineProps<{
zoom?: number,
center?: BMap.Point | BMap.Point[],
}>(), {
zoom: 15,
center: () => new BMap.Point(114.13581398675089, 22.61457979837459)
});
const isMapMount = ref(false);
const emits = defineEmits(['onMapMount']);
let map = shallowRef<BMap.Map | null>(null);
const targetDom = ref<HTMLDivElement>();
// 暴露地图给子组件
provide('map', map);
watch(() => props.zoom, (zoom) => {
if (map.value) {
map.value.setZoom(zoom);
}
})
watch(() => props.center, (position) => {
if (map.value) {
if (Array.isArray(position)) {
map.value.setViewport(position);
} else {
map.value.setViewport({
center: position,
zoom: props.zoom
})
}
}
})
// 初始化地图
onMounted(() => {
if (targetDom.value) {
const _map = new BMap.Map(targetDom.value);
if (Array.isArray(props.center)) {
_map.setViewport(props.center);
} else {
_map.setViewport({
center: props.center,
zoom: props.zoom
})
}
_map.enableScrollWheelZoom();
map.value = _map;
isMapMount.value = true;
emits('onMapMount', _map);
}
})
// 地图销毁
onUnmounted(()=>{
if(map.value){
map.value.destroy();
}
})
</script>
<style lang="less">
.c-BMap {
height: 100%;
width: 100%;
}
// 百度地图logo
.anchorBL {
display: none;
}
</style>
2.2 BMap.Marker 实现
<template></template>
<script lang="ts" setup>
import { watch } from 'vue';
import useBMapOverlay from "./composition/useBMapOverlay";
const props = withDefaults(defineProps<{
position: BMap.Point | null,
icon?: BMap.Icon,
rotation?: number
onClick?: (e: any) => void,
}>(), {
})
const target = new BMap.Marker(props.position || new BMap.Point(0, 0));
if (props.icon) {
target.setIcon(props.icon);
}
target.disableMassClear();
if (props.onClick) {
target.addEventListener('click', props.onClick)
}
useBMapOverlay(target);
watch(() => props.position, (position) => {
if (target) {
target.setPosition(position || new BMap.Point(0, 0));
}
})
watch(() => props.rotation, (rotation) => {
target.setRotation(rotation || 0);
})
</script>
上面有一个函数useBMapOverlay()
,因为后续很多BMap的覆盖物,挂载在 BMap上的步骤相同,我给提取出来了,内容如下:
import { inject, onBeforeUnmount, onMounted, type ShallowRef } from "vue";
const useBMapOverlay = (overlay: BMap.Overlay) => {
// 获取父组件提供的地图,注意必须先生成地图
const map = inject('map') as ShallowRef<BMap.Map | null>;
// 将覆盖物挂载在地图上
onMounted(() => {
if (map.value) {
map.value.addOverlay(overlay);
}
});
// 销毁覆盖物
onBeforeUnmount(() => {
if (map.value) {
map.value.removeOverlay(overlay);
}
})
}
export default useBMapOverlay;
2.3 自定义 Ovarlay
看过百度地图Api的都知道,覆盖物都继承自 BMap.Overlay
只要实现了BMap.Overlay,就可以创建自己的覆盖物
我将 vue 生成的 dom 传递给自定义 Ovarlay,那么这个覆盖物就有了响应式的特点
当然你弄个什么图表 Echart 什么的,一样都可以
<template>
<div ref="targetDom">
<slot></slot>
</div>
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref, watch, type ShallowRef, inject } from 'vue';
import MyOverlay from "./lib/MyOverlay";
const props = withDefaults(defineProps<{
position: BMap.Point | null,
show?: boolean,
offset?: BMap.Size
}>(), {
position: () => new BMap.Point(0, 0),
show: true,
})
let overlay: MyOverlay | null = null;
const targetDom = ref<HTMLDivElement>();
const map = inject('map') as ShallowRef<BMap.Map | null>;
const init = () => {
console.log("创建一次")
if (targetDom.value && map.value && !overlay) {
overlay = new MyOverlay(targetDom.value, props.position || new BMap.Point(0, 0));
map.value.addOverlay(overlay);
if (props.offset) {
overlay.setOffset(props.offset);
}
}
}
watch(() => props.position, (position) => {
if (overlay) {
overlay.setPosition(position || new BMap.Point(0, 0));
}
})
watch(() => props.show, (show) => {
if (overlay) {
if (show) {
overlay.show();
} else {
overlay.hide();
}
}
})
onMounted(init);
onBeforeUnmount(() => {
if (overlay) {
overlay.destroy();
}
})
</script>
export default class MyOverlay extends BMap.Overlay {
private map: any;
private dom: HTMLDivElement;
private position: BMap.Point;
enableMassClear = false;
constructor(dom: HTMLDivElement, position: BMap.Point) {
super();
dom.style.position = 'absolute';
this.dom = dom;
this.position = position;
}
// 使用 BMap.addOverlay 的时候会自动触发本函数
initialize(map: BMap.Map) {
const pane = map.getPanes().markerPane;
if (pane) {
pane.appendChild(this.dom);
}
this.map = map;
return this.dom;
}
private offset: BMap.Size = new BMap.Size(0, 0)
setOffset(offset: BMap.Size) {
this.offset = offset;
this.draw();
}
hide(): void {
this.dom.style.display = 'none';
}
show(): void {
this.dom.style.display = 'block';
}
// 百度地图需要重新绘制的时候触发,比如zoom、地图移动就会触发
draw() {
const pixel = this.map.pointToOverlayPixel(this.position);
const { height, width } = this.offset;
this.dom.style.left = pixel.x + width + "px";
this.dom.style.top = pixel.y + height + "px";
}
setPosition(position: BMap.Point) {
this.position = position;
this.draw();
}
destroy() {
if (this.map) {
this.map.removeOverlay(this);
}
}
}
三、优缺点总结
优点就是大大减少代码
缺点,该系列的组件,本质上套了一层vue,比直接操作js代码,性能上是有问题的,但是问题不是很大,可以牺牲
注意动画,我有这样一个场景,60帧的帧率,Maker 的移动很丝滑,但是自定义 Overlay的移动有点不丝滑
猜测是自定义 Overlay,移动位置是百度地图操作,修改内容是vue操作,这两个动作不同步,所以....