Implementing Drag-to-Select Location with Amap JS API in Vue 3

To integrate drag-based location selection using the Amap JavaScript API within a Vue 3 environment, begin by installing the official loader package:

npm install @amap/amap-jsapi-loader

The core mechanism relies on AMapUI.PositionPicker configured in dragMap mode. This allows the map viewport to move while a fixed visual indicator remains centered, triggering reverse geocoding events whenever the panning action stops.

Below is a complete Vue 3 component implementation using the Composition API. The logic isolates map initialization, handles asynchronous API loading, manages lifecycle cleanup, and binds the position picker to update a reactive list of nearby points of interest.

<template>
  <section class="location-selector">
    <div ref="mapContainerRef" class="viewport"></div>
    <div class="results-panel">
      <ul class="poi-list">
        <li v-for="(place, idx) in nearbyLocations" :key="idx" class="poi-item">
          <strong>{{ place.name }}</strong>
          <span>{{ place.address }}</span>
        </li>
      </ul>
    </div>
  </section>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import AMapLoader from '@amap/amap-jsapi-loader';

const mapContainerRef = ref(null);
const nearbyLocations = ref([]);
let mapInstance = null;
let positionSelector = null;

const setupMap = async () => {
  window._AMapSecurityConfig = {
    securityJsCode: 'YOUR_SECURITY_CODE',
  };

  try {
    const AMap = await AMapLoader.load({
      key: 'YOUR_API_KEY',
      version: '2.0',
      plugins: ['AMap.Geolocation'],
      AMapUI: {
        version: '1.1',
        plugins: ['misc/PositionPicker'],
      },
    });

    mapInstance = new AMap.Map(mapContainerRef.value, {
      resizeEnable: true,
      zoom: 15,
      viewMode: '2D',
    });

    const geoControl = new AMap.Geolocation({
      enableHighAccuracy: true,
      timeout: 8000,
      buttonPosition: 'RB',
      zoomToAccuracy: true,
    });
    mapInstance.addControl(geoControl);

    geoControl.getCurrentPosition((status, res) => {
      if (status === 'complete' && res.position) {
        mapInstance.setCenter(res.position);
      }
    });

    positionSelector = new AMapUI.PositionPicker({
      mode: 'dragMap',
      map: mapInstance,
    });

    positionSelector.on('success', (result) => {
      if (result.regeocode && result.regeocode.pois) {
        nearbyLocations.value = result.regeocode.pois.map((p) => ({
          name: p.name,
          address: p.address,
        }));
      }
    });

    positionSelector.on('fail', (err) => {
      console.warn('Location selection failed:', err);
    });

    positionSelector.start();

    mapInstance.on('click', (evt) => {
      mapInstance.panTo(evt.lnglat);
    });
  } catch (err) {
    console.error('Failed to initialize Amap:', err);
  }
};

onMounted(() => {
  setupMap();
});

onUnmounted(() => {
  if (mapInstance) {
    mapInstance.off('click');
    mapInstance.destroy();
    mapInstance = null;
  }
  if (positionSelector) {
    positionSelector.stop();
  }
});
</script>

<style scoped>
.location-selector {
  display: flex;
  flex-direction: column;
  height: 100vh;
  background: #f5f7fa;
}

.viewport {
  flex: 1;
  width: 100%;
  min-height: 50vh;
}

.results-panel {
  height: 50vh;
  overflow-y: auto;
  background: #ffffff;
  border-top: 1px solid #e0e0e0;
  padding: 1rem;
}

.poi-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.poi-item {
  padding: 0.75rem 0;
  border-bottom: 1px dashed #eeeeee;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.poi-item strong {
  font-size: 0.95rem;
  color: #2c3e50;
}

.poi-item span {
  font-size: 0.8rem;
  color: #7f8c8d;
}
</style>

The implementation hinges on three primary mechanisms:

  1. Asynchronous API Resolution: @amap/amap-jsapi-loader handles script injection and returns a promise that resolves with the AMap namespace. This prevents global scope pollution and ensures all requested plugins are fuly loaded before map instantiation.
  2. PositionPicker in Drag Mode: By setting mode: 'dragMap', the UI library locks a visual indicator to the center of the viewport. When the user stops panning, the library automatically fires a reverse geocoding request. The success event payload contains regeocode.pois, which is mapped to a reactive array for UI rendering.
  3. Lifecycle Management: Map instances and UI controls consume significant memory and attach global event listeners. The onUnmounted hook explicitly detaches click handlers, stops the position picker, and calls destroy() on the map instance to prevent memory leaks during route transitions.

Tags: Vue 3 Amap javascript geolocation Frontend Development

Posted on Mon, 29 Jun 2026 16:52:50 +0000 by aurigus