如何在Mapbox中仅当Popup未打开时监听地图点击事件?
优化Mapbox地图点击请求逻辑:避免Popup打开时触发请求
我有一个功能完善的Map组件,目前存在一个小细节问题:监听了Mapbox地图的点击事件,点击地图任意位置都会发起fetch请求,但希望Popup处于打开状态时,该请求不会触发。目前已通过添加布尔值popupOpen监听弹窗状态,判断弹窗打开时不执行API请求的方式实现需求,但认为该方案并非最优,寻求更好的解决办法。
Map.vue
<template> <AVPage> <div style="height: 85vh"> <MapboxMap style="height: 100%" :accessToken="mapboxToken" :mapStyle="mapboxStyle" @mb-click="map.getGeocodeData($event)" @mb-created=" (instance) => map.setInstance(instance, mapboxToken) " > <MapboxGeolocateControl position="bottom-right" /> <MapboxMarker v-for="(result, index) in map?.locations" :lng-lat="[result.location[0], result.location[1]]" :key="index" popup icon="bar" > <template v-slot:popup> <LocationCard :location="result" :map="map" /> </template> </MapboxMarker> <MapboxNavigationControl position="bottom-right" /> <MapboxPopup v-if="Object.keys(map.poi).length" :lng-lat="[ map.poi.geometry.location.lng, map.poi.geometry.location.lat ]" @mb-open="() => map.popupOpen = true" @mb-close="map.clearPoi()" > <LocationCard :location="map.poi" :map="map" /> </MapboxPopup> </MapboxMap> </div> </AVPage> </template> <script setup> import { ref, computed } from "vue" import "@mapbox/mapbox-gl-geocoder/lib/mapbox-gl-geocoder.css" import "mapbox-gl/dist/mapbox-gl.css" import { MapboxMap, MapboxMarker, MapboxPopup, MapboxNavigationControl, MapboxGeolocateControl } from "@studiometa/vue-mapbox-gl" import LocationCard from "./components/LocationCard.vue" import Map from "src/pinia/models/Map.js" const map = ref(new Map(0, 0)) const mapboxToken = computed(() => process.env.MAPBOX_TOKEN) const mapboxStyle = computed(() => process.env.MAPBOX_STYLE) </script> <style lang="scss"> .mapboxgl-popup-content { border-radius: 5px; overflow: hidden; } </style>
Map.js
import { api } from "src/boot/axios.js" import { buildQueryString } from "src/helpers.js" import Model from "./Model" class Map extends Model { instance = null token = null lat = null lng = null locations = [] poi = {} fetching = true popupOpen = false constructor(lat, lng) { super() this.lat = lat this.lng = lng } clearPoi() { this.poi = {} } setInstance(instance, token) { this.instance = instance this.token = token navigator.geolocation.getCurrentPosition( (data) => this.successCallback(data), (error) => this.errorCallback(error) ) } async getLocations() { const { data } = await api.get( `/pet-places?lat=${this.lat}&lng=${this.lng}` ) data.data.forEach((item) => { this.locations.push(item) }) this.instance.flyTo({ center: [this.lng, this.lat], zoom: 15 }) this.fetching = false } async postRating(lat, lng, name, rating) { const { data } = await api.post("/pet-places", { lat, lng, name, rating }) data.data.forEach((location) => { this.locations.push(location) }) } async successCallback({ coords }) { const { latitude, longitude } = coords this.lat = latitude this.lng = longitude this.getLocations() } errorCallback(err) { console.log("error", err) } async getGeocodeData(e) { if (this.fetching || Object?.keys(this?.poi)?.length) return try { this.fetching = true const { lngLat, originalEvent } = e if (originalEvent.target.tagName !== "CANVAS") { return } else { if (this.popupOpen) { this.popupOpen = false return } } const { lng, lat } = lngLat const queryString = { access_token: this.token, type: "poi" } const response = await fetch( `https://api.mapbox.com/geocoding/v5/mapbox.places/${lng},${lat}.json${buildQueryString( queryString )}`, { method: "GET" } ) const results = await response.json() const { features } = results const poi = features.find( (feature) => feature.id.includes("poi") || feature.id.includes("address") ) if (!poi) { return } const { data } = await api.get( `/location/places?coordinates=${lat},${lng}` ) const { results: locations } = data.data this.poi = locations[0] ?? {} } finally { this.fetching = false } } } export default Map
优化方案
方案1:复用现有poi状态,删除冗余变量
你的代码中已经通过Object.keys(map.poi).length控制Popup的显示,完全可以复用这个状态判断弹窗是否打开,不需要额外维护popupOpen变量。
修改getGeocodeData中的判断逻辑:
async getGeocodeData(e) { if (this.fetching || Object.keys(this.poi).length) return try { this.fetching = true const { lngLat, originalEvent } = e if (originalEvent.target.tagName !== "CANVAS") { return } // 替换原有popupOpen判断:如果poi存在(弹窗打开),清空poi并返回 if (Object.keys(this.poi).length) { this.clearPoi() return } // 剩余原有逻辑... } finally { this.fetching = false } }
同时删除:
- Map类中的
popupOpen = false变量 - 模板中
MapboxPopup的@mb-open="() => map.popupOpen = true"绑定
这样既实现需求,又减少了不必要的状态维护,逻辑更简洁。
方案2:直接通过Mapbox实例判断弹窗状态
Mapbox地图实例提供了getPopups()方法,可以获取所有已添加的弹窗,通过isOpen()判断是否有弹窗处于打开状态:
async getGeocodeData(e) { if (this.fetching || Object?.keys(this?.poi)?.length) return try { this.fetching = true const { lngLat, originalEvent } = e // 判断是否有打开的弹窗 const hasOpenPopup = this.instance.getPopups().some(popup => popup.isOpen()) if (hasOpenPopup) { this.instance.getPopups().forEach(popup => popup.remove()) this.clearPoi() return } if (originalEvent.target.tagName !== "CANVAS") { return } // 剩余原有逻辑... } finally { this.fetching = false } }
这个方案适合弹窗来源较多的场景,不需要依赖业务状态,直接从地图实例获取真实弹窗状态。
内容的提问来源于stack exchange,提问作者Riza Khan
相关产品推荐
相关产品推荐

