---
title: "MaplibreSwipe"
description: "卷帘对比，在主图内叠加一层对照地图，拖动分隔条对照同一视野。"
canonical_url: "https://maplibre.mhaibaraai.cn/docs/extensions/swipe"
---
# MaplibreSwipe

> 卷帘对比，在主图内叠加一层对照地图，拖动分隔条对照同一视野。

## 简介

`MaplibreSwipe` 作为 `MaplibreMap` 的子组件，在主图内叠加一层对照地图，并按分隔条裁切。对照内容放在默认插槽中，例如 `MaplibreTiandituLayer`、`MaplibreLayer`，也可以用 `map-style` 指定整套样式。对照图单向跟随主图的相机、尺寸与投影，拖拽、缩放等交互都由主图响应。

- **层级**：对照图位于主图画布之上、主图控件之下。主图控件与版权位置固定、始终可见；对照图的版权紧贴分隔条，只标注露出的画面。
- **按需开启**：用 `v-if` 开关，主图的图层、控件与相机都不受影响。
- **对照图交互**：露出区域内的鼠标事件会转发给对照图，插槽内的图层事件、`MaplibreTooltip`、`MaplibrePopup` 都可用。

> [!NOTE]
> 
> 主图上被对照图遮住的要素仍会收到事件，需要互斥时可以用暴露的 
> 
> isPointRevealed
> 
>  判断，见「对照图交互」示例。对照图只裁切画布：它的 Marker 不随分隔条隐藏，Popup 靠近分隔条时也不会被截断。

## 用法

在主图内放置 `MaplibreSwipe`，插槽内叠加天地图影像：

```vue [SwipeExample.vue]
<script setup lang="ts">
const position = ref(0.5)
</script>

<template>
  <div class="h-115 w-full overflow-hidden rounded-(--ui-radius) border border-default">
    <MaplibreMap :options="{ style: 'https://tiles.openfreemap.org/styles/liberty', center: [116.397, 39.908], zoom: 11 }">
      <MaplibreNavigationControl position="top-right" />
      <MaplibreScaleControl position="bottom-left" />
      <MaplibreSwipe v-model:position="position">
        <MaplibreTiandituLayer layer="img" annotation />
      </MaplibreSwipe>
    </MaplibreMap>
  </div>
</template>
```

## 示例

### 按钮开启卷帘

用 `v-if` 按需挂载，关闭后对照图随之销毁，主图保持原状：

```vue [SwipeToggleExample.vue]
<script setup lang="ts">
import type { FeatureCollection } from 'geojson'

const active = ref(false)

const data: FeatureCollection = {
  type: 'FeatureCollection',
  features: [
    { type: 'Feature', properties: {}, geometry: { type: 'Point', coordinates: [116.397, 39.908] } },
    { type: 'Feature', properties: {}, geometry: { type: 'Point', coordinates: [116.45, 39.93] } },
    { type: 'Feature', properties: {}, geometry: { type: 'Point', coordinates: [116.35, 39.88] } }
  ]
}
</script>

<template>
  <div class="relative h-115 w-full overflow-hidden rounded-(--ui-radius) border border-default">
    <MaplibreMap :options="{ style: 'https://tiles.openfreemap.org/styles/positron', center: [116.4, 39.9], zoom: 11 }">
      <MaplibreNavigationControl position="top-right" />
      <MaplibreLayer
        layer-id="swipe-toggle-points"
        type="circle"
        :source="{ type: 'geojson', data }"
        :paint="{ 'circle-radius': 8, 'circle-color': '#f43f5e', 'circle-stroke-width': 2, 'circle-stroke-color': '#fff' }"
      />
      <MaplibreSwipe v-if="active">
        <MaplibreTiandituLayer layer="img" annotation />
      </MaplibreSwipe>
    </MaplibreMap>
    <UButton
      class="absolute left-2 top-2 z-10"
      size="sm"
      :icon="active ? 'i-lucide-x' : 'i-lucide-columns-2'"
      :variant="active ? 'soft' : 'solid'"
      @click="active = !active"
    >
      {{ active ? '关闭卷帘' : '卷帘对比' }}
    </UButton>
  </div>
</template>
```

### 上下对比

`orientation="horizontal"` 使用水平分隔条，`map-style` 指定对照图的整套样式：

```vue [SwipeHorizontalExample.vue]
<template>
  <div class="h-115 w-full overflow-hidden rounded-(--ui-radius) border border-default">
    <MaplibreMap :options="{ style: 'https://tiles.openfreemap.org/styles/positron', center: [121.47, 31.23], zoom: 10 }">
      <MaplibreNavigationControl position="top-right" />
      <MaplibreSwipe
        orientation="horizontal"
        :position="0.4"
        map-style="https://tiles.openfreemap.org/styles/dark"
      />
    </MaplibreMap>
  </div>
</template>
```

### 对照图交互

悬浮露出区域内的对照图层会弹出 Tooltip，点击两侧的片区分别由各自的图层响应。主图图层用 `isPointRevealed` 忽略被遮住区域内的点击：

```vue [SwipeInteractionExample.vue]
<script setup lang="ts">
import type { FeatureCollection } from 'geojson'
import type { MapLayerMouseEvent } from 'maplibre-gl'

const swipe = useTemplateRef<{ isPointRevealed: (point: { x: number, y: number }) => boolean }>('swipe')
const clicked = ref('')

const districts: FeatureCollection = {
  type: 'FeatureCollection',
  features: [
    { type: 'Feature', properties: { name: '东城片区' }, geometry: { type: 'Polygon', coordinates: [[[116.40, 39.88], [116.44, 39.88], [116.44, 39.93], [116.40, 39.93], [116.40, 39.88]]] } },
    { type: 'Feature', properties: { name: '西城片区' }, geometry: { type: 'Polygon', coordinates: [[[116.34, 39.88], [116.38, 39.88], [116.38, 39.93], [116.34, 39.93], [116.34, 39.88]]] } }
  ]
}

// 主图图层与对照图层重叠：被卷帘遮住的主图要素不响应点击
function onMainClick(event: MapLayerMouseEvent) {
  if (swipe.value?.isPointRevealed(event.point)) return
  clicked.value = `主图：${event.features?.[0]?.properties?.name}`
}

function onOverlayClick(event: MapLayerMouseEvent) {
  clicked.value = `对照图：${event.features?.[0]?.properties?.name}`
}
</script>

<template>
  <div class="relative h-115 w-full overflow-hidden rounded-(--ui-radius) border border-default">
    <MaplibreMap :options="{ style: 'https://tiles.openfreemap.org/styles/positron', center: [116.39, 39.905], zoom: 12 }">
      <MaplibreLayer
        layer-id="swipe-main-districts"
        type="fill"
        :source="{ type: 'geojson', data: districts }"
        :paint="{ 'fill-color': '#3b82f6', 'fill-opacity': 0.35 }"
        @click="onMainClick"
      />
      <MaplibreSwipe ref="swipe">
        <MaplibreTiandituLayer layer="img" />
        <MaplibreLayer
          layer-id="swipe-overlay-districts"
          type="fill"
          :source="{ type: 'geojson', data: districts }"
          :paint="{ 'fill-color': '#f59e0b', 'fill-opacity': 0.45 }"
          @click="onOverlayClick"
        />
        <MaplibreTooltip layer-id="swipe-overlay-districts">
          <template #default="{ feature }">
            {{ feature?.properties?.name }}（对照图）
          </template>
        </MaplibreTooltip>
      </MaplibreSwipe>
    </MaplibreMap>
    <UBadge v-if="clicked" class="absolute left-2 top-2 z-10" color="neutral" variant="subtle">
      {{ clicked }}
    </UBadge>
  </div>
</template>
```

## API

### Props

```ts
/**
 * Props for the MaplibreSwipe component
 */
interface MaplibreSwipeProps {
  /**
   * 分隔方向：vertical 为竖直分隔条左右对比，horizontal 为水平分隔条上下对比
   * @default 'vertical'
   */
  orientation?: "vertical" | "horizontal" | undefined;
  /**
   * 方向键每次移动的比例
   * @default 0.05
   */
  step?: number | undefined;
  /**
   * 对照图的底图样式（对象或样式 URL）；省略时为空白样式，由插槽内图层提供内容
   */
  mapStyle?: string | StyleSpecification | undefined;
  /**
   * 分隔条位置，0..1
   * @default 0.5
   */
  position?: number | undefined;
}
```

### Emits

```ts
/**
 * Emitted events for the MaplibreSwipe component
 */
interface MaplibreSwipeEmits {
  update:position: (payload: [value: number]) => void;
}
```

### Slots

```ts
/**
 * Slots for the MaplibreSwipe component
 */
interface MaplibreSwipeSlots {
  /**
   * 对照内容：放置 MaplibreTiandituLayer、MaplibreLayer 等，作用于对照图
   */
  default(): any;
}
```

### Expose

| 名称 | 类型 | 说明 |
| --- | --- | --- |
| `isPointRevealed` | `(point: { x: number, y: number }) => boolean` | 主图容器内的点是否落在对照图露出区域 |

## Changelog

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


## Sitemap

See the full [sitemap](https://maplibre.mhaibaraai.cn/sitemap.md) for all pages.
