地图标注用到的主要是Overlay
这里做了优化,通过手动添加,删除元素来达到提升性能效果。
监听地图视野的改变来动态变化标注数量的加载,让程序仅渲染视野内的标注信息。
<template>
<div ref="rootRef" style="display: none"></div>
</template>
<script setup>
import { ref, watch, onUnmounted } from 'vue'
import Overlay from 'ol/Overlay'
import { fromLonLat } from 'ol/proj'
const props = defineProps({
map: { type: Object, required: true },
markers: { type: Array, default: () => [] },
titleField: { type: String, default: 'name' },
debounceTime: { type: Number, default: 100 },
autoPan: { type: Boolean, default: true }
})
const activeOverlays = new Map()
let moveEndKey = null
// ---------- 工具函数 ----------
function debounce(fn, delay) {
let timer
return function (...args) {
clearTimeout(timer)
timer = setTimeout(() => fn.apply(this, args), delay)
}
}
function buildMarkerElement(marker) {
const el = document.createElement('div')
el.className = 'marker-layer-item'
const title = marker[props.titleField] || '●'
el.innerHTML = `<div class="marker-card"><strong>${title}</strong></div>`
el.addEventListener('click', () => console.log('点击标注:', marker))
return el
}
function addMarker(marker) {
const element = buildMarkerElement(marker)
const overlay = new Overlay({
element,
position: fromLonLat([marker.lon, marker.lat]),
positioning: 'bottom-center',
offset: [0, -6],
autoPan: props.autoPan,
stopEvent: false
})
props.map.addOverlay(overlay)
activeOverlays.set(marker.id, { overlay, element })
}
function removeMarker(id) {
const item = activeOverlays.get(id)
if (!item) return
props.map.removeOverlay(item.overlay)
item.element.remove()
activeOverlays.delete(id)
}
// ---------- 视野裁剪更新 ----------
const updateVisibleMarkers = debounce(() => {
const map = props.map
if (!map) return
map.updateSize()
const size = map.getSize()
if (!size || size[0] === 0 || size[1] === 0) return
const extent = map.getView().calculateExtent(size)
const visibleIds = new Set()
props.markers.forEach(m => {
if (!m || m.lon === undefined || m.lat === undefined) return
const coord = fromLonLat([m.lon, m.lat])
if (
coord[0] >= extent[0] && coord[0] <= extent[2] &&
coord[1] >= extent[1] && coord[1] <= extent[3]
) {
visibleIds.add(m.id)
}
})
activeOverlays.forEach((_, id) => {
if (!visibleIds.has(id)) removeMarker(id)
})
visibleIds.forEach(id => {
if (!activeOverlays.has(id)) {
const marker = props.markers.find(x => x.id === id)
if (marker) addMarker(marker)
}
})
}, props.debounceTime)
// ---------- 初始化(地图就绪时立刻添加标注)----------
function initMarkers() {
const map = props.map
if (!map) return
// 强制确保尺寸
map.updateSize()
const size = map.getSize()
if (!size || size[0] === 0 || size[1] === 0) {
// 尺寸仍无效,等待 postrender 事件(仅一次)
const onReady = () => {
map.un('postrender', onReady)
initMarkers() // 再次尝试
}
map.on('postrender', onReady)
return
}
// 尺寸有效,执行首次标注更新,并绑定移动事件
updateVisibleMarkers()
if (!moveEndKey) {
moveEndKey = map.on('moveend', updateVisibleMarkers)
}
}
// ---------- 监听 map 变化 ----------
watch(() => props.map, (newMap, oldMap) => {
if (oldMap) {
if (moveEndKey) oldMap.un('moveend', moveEndKey)
moveEndKey = null
activeOverlays.forEach((_, id) => removeMarker(id))
}
if (newMap) {
initMarkers()
}
}, { immediate: true })
// ---------- 监听 markers 变化 ----------
watch(() => props.markers, () => updateVisibleMarkers(), { deep: true })
// ---------- 清理 ----------
onUnmounted(() => {
if (props.map && moveEndKey) {
props.map.un('moveend', moveEndKey)
}
activeOverlays.forEach((_, id) => removeMarker(id))
activeOverlays.clear()
})
</script>
<style>
.marker-layer-item {
position: absolute;
white-space: nowrap;
pointer-events: auto;
transform: translate(-50%, -100%);
}
.marker-card {
background: white;
border-radius: 6px;
padding: 4px 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
font-size: 12px;
font-family: sans-serif;
cursor: pointer;
user-select: none;
position: relative;
}
.marker-card::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 5px solid transparent;
border-top-color: white;
}
</style>