---
title: "Raw Map Instance"
description: "直接调用 MapLibre Map 以处理图层事件、筛选、悬停状态和相机动画。"
canonical_url: "https://nuxt-scripts.zhcndoc.com/scripts/maplibre/guides/raw-map-instance"
last_updated: "2026-09-23T10:33:14.787Z"
---

组件负责声明地图资源。与这些资源交互是 MapLibre 的工作。获取 `Map` 并直接调用它来处理图层事件、`setFilter`、要素状态和相机动画。

## 获取实例

`ScriptMapLibreMap` 会通过其 expose 对象触发 `ready`。`map` 字段是一个浅层 ref，保存着 MapLibre `Map`。

```vue
<script setup lang="ts">
import type { Map as MapLibreMap } from 'maplibre-gl'
import type { ShallowRef } from 'vue'

function onMapReady({ map }: { map: ShallowRef<MapLibreMap | undefined> }) {
  const instance = map.value
  if (!instance)
    return

  instance.setMaxZoom(18)
}
</script>

<template>
  <ScriptMapLibreMap
    :center="[144.9631, -37.8136]"
    map-style="https://tiles.openfreemap.org/styles/liberty"
    @ready="onMapReady"
  />
</template>
```

模板 ref 的用法相同。`ScriptMapLibreMap` 会暴露 `maplibre`、`map` 和 `load`。

::callout{icon="i-heroicons-information-circle"}
`ready` 会在 MapLibre 的 `load` 事件中触发。MapLibre 会先加载样式，然后触发 `load`。因此，在处理函数中调用 `addSource` 和 `addLayer` 是安全的。你无需等待任何内容。
::

## 样式变更后重新添加资源

运行 `setStyle` 时，MapLibre 会移除所有 source 和图层。更改 `map-style` prop 会调用 `setStyle`，因此你添加到原始实例上的内容都会消失。监听 `style.load` 并重新添加。

```ts
function onMapReady({ map }: { map: ShallowRef<MapLibreMap | undefined> }) {
  const instance = map.value
  if (!instance)
    return

  function addRouteLayer() {
    instance!.addSource('route', { type: 'geojson', data: route })
    instance!.addLayer({
      id: 'route-line',
      type: 'line',
      source: 'route',
      paint: { 'line-color': '#2563eb', 'line-width': 4 },
    })
  }

  addRouteLayer()
  instance.on('style.load', addRouteLayer)
}
```

`ScriptMapLibreGeoJson` 已经会恢复自身的 source 和图层。只有你自己添加的资源需要这样处理。

## 筛选图层而不重建它

更改 `layers` prop 会重建 source 及其图层。若要筛选已渲染的图层，请在地图上调用 `setFilter`。

```ts
watch(selectedCategory, (category) => {
  const instance = map.value
  if (!instance || !instance.getLayer('depot-point'))
    return

  instance.setFilter('depot-point', category
    ? ['==', ['get', 'category'], category]
    : null)
})
```

传入 `null` 可清除筛选器。

## 使用要素状态实现悬停和选择

要素状态需要要素 ID。GeoJSON 要素默认没有 ID，因此请在 `sourceOptions` 中设置 `promoteId` 或 `generateId`。

```vue
<script setup lang="ts">
const depotSourceOptions = { promoteId: 'depotId' }
</script>

<template>
  <ScriptMapLibreGeoJson
    source-id="depots"
    :data="depots"
    :source-options="depotSourceOptions"
    :layers="depotLayers"
  />
</template>
```

悬停时写入状态，离开时清除状态。`<ScriptMapLibreGeoJson>`{lang="html"} 会为自身图层触发 `mousemove` 和 `mouseleave`，因此你也可以在模板中绑定这些事件。请参阅[指针事件](/scripts/maplibre/api/geojson#pointer-events)。

```ts
let hoveredId: string | number | undefined

instance.on('mousemove', 'depot-point', (event) => {
  const id = event.features?.[0]?.id
  if (id === undefined)
    return

  if (hoveredId !== undefined)
    instance.setFeatureState({ source: 'depots', id: hoveredId }, { hover: false })

  hoveredId = id
  instance.setFeatureState({ source: 'depots', id }, { hover: true })
  instance.getCanvas().style.cursor = 'pointer'
})

instance.on('mouseleave', 'depot-point', () => {
  if (hoveredId !== undefined)
    instance.setFeatureState({ source: 'depots', id: hoveredId }, { hover: false })

  hoveredId = undefined
  instance.getCanvas().style.cursor = ''
})
```

图层会通过 `feature-state` 表达式读取状态：

```ts
const hoverPaint: CircleLayerSpecification['paint'] = {
  'circle-radius': ['case', ['boolean', ['feature-state', 'hover'], false], 11, 7],
}
```

::callout{icon="i-heroicons-exclamation-triangle" color="amber"}
为每个要素指定一个既不是 `0` 也不是 `''` 的 ID。MapLibre 可以正确绘制 ID 为假值的要素，但 `queryRenderedFeatures` 会将其状态报告为空。`loadMatchingFeature` 会通过真值检查来保护该查找。因此，任何读取 `event.features[0].state` 的处理函数都得不到任何结果，依赖状态的命中测试也会失效。如果你提升的是从零开始的索引，请将其加一。
::

## 读取视口中的要素

`queryRenderedFeatures` 会返回 MapLibre 当前绘制的内容。使用它可以让侧边列表与视口保持同步。

```ts
instance.on('moveend', () => {
  const visible = instance.queryRenderedFeatures({ layers: ['depot-point'] })
  visibleDepots.value = visible.map(feature => feature.properties.depotId as string)
})
```

`queryRenderedFeatures` 会读取已渲染的瓦片。被瓦片边缘裁剪的要素可能会重复返回。

## 为相机添加动画

`center`、`zoom`、`bearing`、`pitch` 和 `bounds` props 会直接跳转相机位置。如果你想添加动画，请调用地图。若要在不添加动画的情况下框定数据，请改用 [`bounds` prop](/scripts/maplibre/api/script-maplibre-map#frame-the-data)。

```ts
instance.fitBounds([[144.94, -37.83], [144.97, -37.81]], {
  padding: 48,
  duration: 600,
})

instance.easeTo({ center: [144.9631, -37.8136], zoom: 15, duration: 400 })
```

`fitBounds` 接受 `[[west, south], [east, north]]`。若要执行较长的移动并让视角先拉远再返回，请使用 `flyTo`。

## 清理监听器

MapLibre v6 中的 `map.on()`{lang="ts"} 会返回一个订阅对象。保存该对象，并在组件卸载时取消订阅。

```ts
const subscription = instance.on('click', 'depot-point', onDepotClick)

onBeforeUnmount(() => subscription.unsubscribe())
```

## Sitemap

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