将自定义vue组件加载在Mapbox或Maplibre的marker和popup上
1. 使用场景
在开发WebGIS应用时,我们常需要将自定义UI组件与地图元素结合。本文介绍如何将Vue组件集成到Mapbox/Maplibre的Marker标记点和Popup弹窗中,实现动态交互式的地图功能。
2. 为什么需要特殊处理?
在常规开发中我们大多的处理是
const tipsEle = document.createElement("div")
tipsEle.setAttribute("class", "tips")
tipsEle.style.color = "#e73f32"
tipsEle.style.fontSize = "12px"
tipsEle.style.fontWeight = "400"
tipsEle.innerHTML = "删除"
const tipsOption = {
element: tipsEle,
anchor: "left",
offset: [-45, 30],
}
new maplibregl.Marker(tipsOption).setLngLat(coords).addTo(map)
这样的写法显而易见维护难,调试难,ui在复杂一点复杂程度也跟着上去了,如果换成组件形式的话,所有问题迎刃而解了。
3. 使用createVNode实现
/**
* @description: 创建地图Marker 和 Popup 组件, 渲染自定义组件
* @author: jihai
* @createTime: 2025/03/05 15:05:01
*/
import {createVNode, render} from "vue";
import * as maplibregl from 'maplibre-gl';
class CreateVNode{
declare map:any
declare coordinates:any
declare markerComponentId: string
declare marker:any
declare markerElement: any
declare popupComponentId: string
declare popup:any
declare popupElement: any
constructor(option: {
map: any,
coordinates: any
}) {
const {map, coordinates} = option
this.map = map
this.coordinates = coordinates
this.markerComponentId = `markerComponentId-${performance.now().toString()}`
this.marker = null
this.markerElement = null
this.popupComponentId = `popupComponentId-${performance.now().toString()}`
this.popup = null
this.popupElement = null
}
createMarker(options: {
component: {
component: any, // 自定义vue组件
props: any // 自定义组件props
},
config?: {
offset: number[],
anchor: string
}
}){
const { component, config } = options
const app = createVNode(component?.component, {data: component?.props})
this.markerElement = document.createElement("div")
this.markerElement.setAttribute("id", this.markerComponentId)
this.marker = new maplibregl.Marker({
element: this.markerElement,
anchor: config?.anchor ?? 'bottom',
offset: config?.offset ?? [0, 0]
}).setLngLat(this.coordinates).addTo(this.map)
render(app, this.markerElement)
}
createPopup(options: {
component: {
component: any, // 自定义vue组件
props: any // 自定义vue组件props
},
config?: {
offset: number[],
anchor: string,
closeButton: boolean
}
}){
const { component, config } = options
const app = createVNode(component?.component, {data: component?.props})
this.popupElement = document.createElement("div")
this.popupElement.setAttribute("id", this.popupComponentId)
this.popup = new maplibregl.Popup(
{
className: 'custom-popup',
closeButton: config?.closeButton ?? true,
anchor: config?.anchor ?? 'bottom'
})
.setMaxWidth('none')
.setOffset(config?.offset ?? [0, 0]) // 这里设置偏移量
.setLngLat(this.coordinates)
.setDOMContent(this.popupElement)
.addTo(this.map)
render(app, this.popupElement)
}
removeMarker(){
if(this.marker){
this.marker.remove()
}
if(this.markerElement){
this.markerElement.remove()
}
}
removePopup(){
if(this.popup){
this.popup.remove()
}
if(this.popupElement){
this.popupElement.remove()
}
}
remove(){
this.removeMarker()
this.removePopup()
}
}
export default CreateVNode
4. 使用方法
// 简单使用 具体使用根据业务场景合理清除图层
let marker = new CreateVNode({
map: map,
coordinates: coordinates
})
marker.createMarker({
component: {
component: markerComp,
props: markerProps
},
config: {
offset: [0, 120],
anchor: 'bottom'
}
})
// 图层清除
marker.removeMarker()
marker = null
5. 效果展示
红色框里为element组件渲染结果