You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 20:01:10