---
title: "MapboxMarker"
description: "标记，默认插槽渲染自定义 DOM，支持 v-model:lnglat 双向绑定、拖拽回写与内建弹窗。"
seo_title: "MapboxMarker Component"
seo_description: "MapboxMarker renders a mapbox-gl marker with custom DOM via the default slot, two-way v-model:lnglat binding, drag-to-update support and a built-in popup slot."
canonical_url: "https://mapbox.mhaibaraai.cn/docs/core/marker"
---
# MapboxMarker

> 标记，默认插槽渲染自定义 DOM，支持 v-model:lnglat 双向绑定、拖拽回写与内建弹窗。

## 简介

`MapboxMarker` 在地图上放置一个标记：默认插槽提供自定义 DOM（省略则用 mapbox 默认水滴图标），位置经 `v-model:lnglat` 双向绑定。`options.draggable` 开启拖拽后，拖拽结束会把新坐标回写到绑定值。

提供 `#popup` 插槽即可挂载弹窗，内容为任意 Vue 组件；`trigger` 决定触发时机（`click` / `hover` / `none`），开合状态由 `v-model:open` 暴露。

## 用法

无插槽时使用默认标记，`v-model:lnglat` 控制位置：

```vue [MarkerBasicExample.vue]
<script setup lang="ts">
import type { LngLatLike } from 'mapbox-gl'

const position = ref<LngLatLike>([116.397, 39.908])
</script>

<template>
  <div class="h-115 w-full overflow-hidden rounded-(--ui-radius) border border-default">
    <MapboxMap :options="{ style: 'mapbox://styles/mapbox/streets-v12', center: [116.397, 39.908], zoom: 12 }">
      <!-- 无插槽时使用 mapbox 默认水滴标记 -->
      <MapboxMarker v-model:lnglat="position" />
    </MapboxMap>
  </div>
</template>
```

## 示例

### 自定义内容与拖拽

默认插槽渲染自定义 DOM，开启 `draggable` 后拖拽实时回写坐标：

```vue [MarkerDraggableExample.vue]
<script setup lang="ts">
import type { LngLatLike } from 'mapbox-gl'

const position = ref<LngLatLike>([116.397, 39.908])

const label = computed(() => {
  const [lng, lat] = position.value as [number, number]
  return `${lng.toFixed(3)}, ${lat.toFixed(3)}`
})
</script>

<template>
  <div class="h-115 w-full overflow-hidden rounded-(--ui-radius) border border-default">
    <MapboxMap :options="{ style: 'mapbox://styles/mapbox/streets-v12', center: [116.397, 39.908], zoom: 12 }">
      <!-- 默认插槽自定义 DOM；draggable 时拖拽结束回写 v-model:lnglat -->
      <MapboxMarker v-model:lnglat="position" :options="{ draggable: true }">
        <div class="flex size-8 items-center justify-center rounded-full border-2 border-white bg-primary text-white shadow-lg">
          <UIcon name="i-lucide-map-pin" class="size-4" />
        </div>
      </MapboxMarker>
      <div class="absolute left-3 top-3 z-10 rounded-(--ui-radius) border border-default bg-default/80 px-3 py-1.5 text-sm backdrop-blur">
        拖拽标记：{{ label }}
      </div>
    </MapboxMap>
  </div>
</template>
```

### 点击弹出自定义内容 `v1.2.0+`

提供 `#popup` 插槽后，点击标记即开合弹窗；插槽暴露 `close` 供内容主动收起：

```vue [MarkerPopupExample.vue]
<script setup lang="ts">
import type { LngLatLike } from 'mapbox-gl'

const position = ref<LngLatLike>([116.397, 39.908])
</script>

<template>
  <div class="h-115 w-full overflow-hidden rounded-(--ui-radius) border border-default">
    <MapboxMap :options="{ style: 'mapbox://styles/mapbox/streets-v12', center: [116.397, 39.908], zoom: 13 }">
      <MapboxMarker :lnglat="position" :popup-options="{ offset: 20 }">
        <div class="flex size-8 cursor-pointer items-center justify-center rounded-full border-2 border-white bg-primary text-white shadow-lg">
          <UIcon name="i-lucide-map-pin" class="size-4" />
        </div>
        <template #popup="{ close }">
          <div class="w-48 px-1 py-0.5">
            <p class="font-semibold">
              天安门
            </p>
            <p class="mt-0.5 text-sm text-muted">
              北京市东城区长安街
            </p>
            <UButton class="mt-2" size="xs" color="neutral" variant="subtle" @click="close">
              知道了
            </UButton>
          </div>
        </template>
      </MapboxMarker>
    </MapboxMap>
  </div>
</template>
```

> \[\!NOTE\]
> 
> trigger="click"
> 
>  时，标记上的点击不会冒泡到地图，因此不触发 
> 
> MapboxMap
> 
>  的 
> 
> click
> 
>  事件。这是必需的：标记元素挂在地图的画布容器内，若放任冒泡，本次点击会触发 mapbox 的 
> 
> preclick
> 
> ，把刚打开的弹窗按 
> 
> closeOnClick
> 
>  立刻关掉。

> \[\!NOTE\]
> 
> trigger="hover"
> 
>  时鼠标移入弹窗会触发标记的 
> 
> mouseleave
> 
>  而关闭 —— 这是弹窗与标记 DOM 分离导致的固有限制。悬浮场景建议配较小的 
> 
> popupOptions.offset
> 
> ，需要在弹窗内交互则用 
> 
> click
> 
> 。

### 一组标记默认展开 `v1.2.0+`

每个标记各自持有 `open` 状态，静态传 `:open="true"` 即默认展开，彼此互不互斥，仍可各自点击开合：

```vue [MarkerGroupPopupExample.vue]
<script setup lang="ts">
interface Poi {
  id: string
  lnglat: [number, number]
  name: string
  type: string
}

const points: Poi[] = [
  { id: 'tam', lnglat: [116.397, 39.908], name: '天安门', type: '地标' },
  { id: 'gm', lnglat: [116.461, 39.909], name: '国贸', type: '商圈' },
  { id: 'zgc', lnglat: [116.316, 39.983], name: '中关村', type: '科技园' }
]
</script>

<template>
  <div class="h-115 w-full overflow-hidden rounded-(--ui-radius) border border-default">
    <MapboxMap :options="{ style: 'mapbox://styles/mapbox/light-v11', center: [116.39, 39.94], zoom: 10.6 }">
      <MapboxMarker
        v-for="point in points"
        :key="point.id"
        :lnglat="point.lnglat"
        :open="true"
        :popup-options="{ offset: 18, closeButton: false, closeOnClick: false }"
      >
        <div class="size-3 cursor-pointer rounded-full border-2 border-white bg-primary shadow" />
        <template #popup>
          <div class="px-1 text-sm">
            <span class="font-semibold">{{ point.name }}</span>
            <span class="text-muted"> · {{ point.type }}</span>
          </div>
        </template>
      </MapboxMarker>
    </MapboxMap>
  </div>
</template>
```

> \[\!TIP\]
> 
> 需要「全局同时只开一个」时，由外部持有单一 
> 
> activeId
> 
>  并绑定 
> 
> v-model:open
> 
>  即可。点位达数百量级时，应改用 symbol 图层的 
> 
> text-field
> 
>  渲染标注，而非渲染 N 个弹窗 DOM。

## API

### Props

```ts
/**
 * Props for the MapboxMarker component
 */
interface MapboxMarkerProps {
  lnglat: mapboxgl.LngLatLike;
  /**
   * 标记选项；element 由默认插槽提供，无需在此传入
   */
  options?: Omit<mapboxgl.MarkerOptions, "element"> | undefined;
  /**
   * 弹窗选项，仅在提供 #popup 插槽时生效
   */
  popupOptions?: mapboxgl.PopupOptions | undefined;
  /**
   * #popup 插槽的触发时机；'none' 表示不绑定监听，完全由 v-model:open 受控
   * @default "\"click\""
   */
  trigger?: PopupTrigger | undefined;
  /**
   * 弹窗开合状态；初始传 true 即默认展开
   * @default "false"
   */
  open?: boolean | undefined;
}
```

### Emits

```ts
/**
 * Emitted events for the MapboxMarker component
 */
interface MapboxMarkerEmits {
  update:lnglat: (payload: [value: mapboxgl.LngLatLike]) => void;
  update:open: (payload: [value: boolean]) => void;
}
```

### Slots

```ts
/**
 * Slots for the MapboxMarker component
 */
interface MapboxMarkerSlots {
  popup(): any;
  default(): any;
}
```

### Expose

通过 [`useTemplateRef`](https://vuejs.org/api/composition-api-helpers.html#usetemplateref){rel="[\"nofollow\"]"} 访问组件实例。

| Name     | Type                                                    |
| -------- | ------------------------------------------------------- |
| `marker` | `() => Marker | undefined`   
 获取底层 mapbox-gl Marker 实例 |

## Changelog

See commit history for [src/runtime/components/MapboxMarker.vue](https://github.com/mhaibaraai/movk-mapbox/commits/main/src/runtime/components/MapboxMarker.vue).


## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
