高德地图 Web JS API 2.0 接入与使用指南

本文整理高德地图 Web JS API 2.0 在原生 JavaScript 和 Vue 3 项目中的接入方式,并给出点标记、信息窗体、定位、地址解析、路线规划等常见功能示例。

本文示例面向中国大陆版 Web JS API 2.0。Key、配额、计费与合规要求可能调整,上线前请以高德开放平台控制台和官方文档为准。

一、接入前准备

1.1 创建应用和 Key

  1. 登录高德开放平台控制台
  2. 进入「应用管理」并创建应用。
  3. 在应用中添加 Key,服务平台选择「Web 端(JS API)」。
  4. 保存生成的 Key安全密钥 securityJsCode
  5. 在控制台配置可使用该 Key 的域名白名单,并为开发、测试、生产环境分别创建 Key。

2021 年 12 月 2 日之后申请的 Web 端 Key 必须配合安全密钥使用。注意不要误选「Web 服务」Key,Web 服务 Key 主要供服务端 REST API 使用。

1.2 安全密钥配置

开发环境可直接配置明文安全密钥,但它最终会出现在浏览器代码中:

1
2
3
window._AMapSecurityConfig = {
securityJsCode: '你的安全密钥',
};

该配置必须在加载 JS API 之前执行。生产环境推荐通过自己的服务器代理服务请求:

1
2
3
window._AMapSecurityConfig = {
serviceHost: 'https://map-api.example.com/_AMapService',
};

其中 /_AMapService 是高德规定的固定前缀。代理服务器负责将请求转发到高德并附加 jscode,不要把安全密钥下发给浏览器。官方提供了 Nginx、Java、Node 等代理思路,可参考安全密钥使用说明

二、原生 JavaScript 快速接入

官方推荐使用 JS API Loader,加载器可以处理异步加载、插件加载和重复请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>高德地图示例</title>
<style>
html,
body,
#map-container {
width: 100%;
height: 100%;
margin: 0;
}
</style>
</head>
<body>
<div id="map-container"></div>

<script>
window._AMapSecurityConfig = {
securityJsCode: '你的安全密钥',
};
</script>
<script src="https://webapi.amap.com/loader.js"></script>
<script>
AMapLoader.load({
key: '你的 Web 端 Key',
version: '2.0',
plugins: ['AMap.Scale', 'AMap.ToolBar'],
})
.then(AMap => {
const map = new AMap.Map('map-container', {
viewMode: '2D',
zoom: 13,
center: [116.397428, 39.90923],
mapStyle: 'amap://styles/normal',
});

map.addControl(new AMap.Scale());
map.addControl(new AMap.ToolBar());
})
.catch(error => {
console.error('高德地图加载失败:', error);
});
</script>
</body>
</html>

地图容器必须已经挂载到 DOM,并且具有明确高度;只有 width: 100% 而没有高度时,地图不会显示。

三、npm 项目接入

3.1 安装 Loader

1
npm install @amap/amap-jsapi-loader

创建统一的加载模块,避免业务组件各自加载不同版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/utils/amap.js
import AMapLoader from '@amap/amap-jsapi-loader';

let loadPromise;

export function loadAMap() {
if (!loadPromise) {
window._AMapSecurityConfig = {
securityJsCode: import.meta.env.VITE_AMAP_SECURITY_CODE,
// 生产环境推荐替换为:
// serviceHost: "https://map-api.example.com/_AMapService",
};

loadPromise = AMapLoader.load({
key: import.meta.env.VITE_AMAP_KEY,
version: '2.0',
plugins: ['AMap.Scale', 'AMap.ToolBar', 'AMap.Geolocation', 'AMap.Geocoder', 'AMap.Driving'],
});
}

return loadPromise;
}

环境变量示例:

1
2
3
# .env.development
VITE_AMAP_KEY=你的开发环境Key
VITE_AMAP_SECURITY_CODE=你的开发环境安全密钥

环境变量只能改善配置管理,不能隐藏发送到浏览器的秘密。生产环境请使用域名白名单和代理方案,不要把 .env 当作安全措施。

四、Vue 3 完整接入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<template>
<div ref="containerRef" class="map-container"></div>
</template>

<script setup>
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
import { loadAMap } from '@/utils/amap';

const containerRef = ref(null);

let AMap = null;
let map = null;
let resizeObserver = null;

onMounted(async () => {
try {
await nextTick();
AMap = await loadAMap();

map = new AMap.Map(containerRef.value, {
viewMode: '2D',
zoom: 13,
center: [116.397428, 39.90923],
resizeEnable: true,
});

map.addControl(new AMap.Scale());
map.addControl(new AMap.ToolBar({ position: 'RB' }));

const marker = new AMap.Marker({
position: [116.397428, 39.90923],
title: '天安门',
});
map.add(marker);

resizeObserver = new ResizeObserver(() => map?.resize());
resizeObserver.observe(containerRef.value);
} catch (error) {
console.error('地图初始化失败:', error);
}
});

onBeforeUnmount(() => {
resizeObserver?.disconnect();
map?.destroy();
map = null;
AMap = null;
});
</script>

<style scoped>
.map-container {
width: 100%;
height: 600px;
}
</style>

在弹窗、折叠面板或 keep-alive 页面中,容器显示状态或尺寸改变后可调用:

1
map.resize();

五、地图实例常用操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 修改中心点和缩放级别
map.setCenter([121.473667, 31.230525]);
map.setZoom(15);
map.setZoomAndCenter(15, [121.473667, 31.230525]);

// 获取当前状态
const center = map.getCenter();
const zoom = map.getZoom();

// 限制显示范围
const bounds = new AMap.Bounds([116.2, 39.7], [116.6, 40.1]);
map.setBounds(bounds);

// 事件监听与移除
function handleMapClick(event) {
console.log(event.lnglat.getLng(), event.lnglat.getLat());
}

map.on('click', handleMapClick);
map.off('click', handleMapClick);

常用事件包括 clickdblclickrightclickmoveendzoomendcomplete

六、覆盖物

6.1 点标记 Marker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const marker = new AMap.Marker({
position: [116.397428, 39.90923],
title: '天安门',
anchor: 'bottom-center',
offset: new AMap.Pixel(0, 0),
draggable: true,
});

map.add(marker);

marker.on('click', event => {
console.log('点击标记:', event.lnglat);
});

marker.setPosition([116.407428, 39.90923]);
map.remove(marker);

多个覆盖物可一次添加,并自动调整视野:

1
2
3
4
5
6
7
8
9
10
const markers = points.map(
point =>
new AMap.Marker({
position: [point.lng, point.lat],
title: point.name,
}),
);

map.add(markers);
map.setFitView(markers, false, [60, 60, 60, 60]);

点位达到数百或数千个时,不要创建大量复杂 DOM Marker,可根据需求选择 LabelsLayerMassMarks 或聚合能力。

6.2 信息窗体 InfoWindow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const infoWindow = new AMap.InfoWindow({
content: `
<div class="map-info-window">
<strong>天安门</strong>
<p>北京市东城区长安街</p>
</div>
`,
anchor: 'bottom-center',
offset: new AMap.Pixel(0, -35),
});

marker.on('click', () => {
infoWindow.open(map, marker.getPosition());
});

// 关闭弹窗
infoWindow.close();

如果 content 包含用户输入,必须先转义或过滤,避免 XSS。

6.3 折线、圆和多边形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const polyline = new AMap.Polyline({
path: [
[116.38, 39.9],
[116.4, 39.91],
[116.42, 39.9],
],
strokeColor: '#1677ff',
strokeWeight: 6,
});

const circle = new AMap.Circle({
center: [116.397428, 39.90923],
radius: 1000,
fillColor: '#1677ff',
fillOpacity: 0.15,
strokeColor: '#1677ff',
});

const polygon = new AMap.Polygon({
path: [
[116.38, 39.9],
[116.41, 39.9],
[116.41, 39.92],
[116.38, 39.92],
],
fillColor: '#52c41a',
fillOpacity: 0.2,
strokeColor: '#52c41a',
});

map.add([polyline, circle, polygon]);
map.setFitView([polyline, circle, polygon]);

七、浏览器定位

使用定位功能前加载 AMap.Geolocation 插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const geolocation = new AMap.Geolocation({
enableHighAccuracy: true,
timeout: 10000,
zoomToAccuracy: true,
position: 'RB',
});

map.addControl(geolocation);

geolocation.getCurrentPosition((status, result) => {
if (status === 'complete') {
const { lng, lat } = result.position;
map.setZoomAndCenter(16, [lng, lat]);
console.log('定位成功:', result);
return;
}

console.error('定位失败:', result.message, result);
});

浏览器精确定位通常要求:

  • 页面使用 HTTPS,localhost 开发环境除外;
  • 用户授予位置权限;
  • 浏览器、系统和网络环境支持定位;
  • 业务对拒绝授权、超时和弱精度结果提供降级方案。

八、地址与坐标互转

使用前加载 AMap.Geocoder 插件。

8.1 地址转经纬度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const geocoder = new AMap.Geocoder({
city: '北京',
});

geocoder.getLocation('北京市东城区天安门', (status, result) => {
if (status === 'complete' && result.info === 'OK') {
const location = result.geocodes[0]?.location;
if (!location) return;

const position = [location.getLng(), location.getLat()];
map.setZoomAndCenter(16, position);
console.log('地址坐标:', position);
} else {
console.error('地理编码失败:', result);
}
});

8.2 经纬度转地址

1
2
3
4
5
6
7
geocoder.getAddress([116.397428, 39.90923], (status, result) => {
if (status === 'complete' && result.info === 'OK') {
console.log(result.regeocode.formattedAddress);
} else {
console.error('逆地理编码失败:', result);
}
});

九、驾车路线规划

页面增加结果面板:

1
2
<div id="map-container"></div>
<div id="route-panel"></div>

加载 AMap.Driving 插件后执行搜索:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const driving = new AMap.Driving({
map,
panel: 'route-panel',
hideMarkers: false,
autoFitView: true,
});

const start = new AMap.LngLat(116.379028, 39.865042);
const end = new AMap.LngLat(116.427281, 39.903719);

driving.search(start, end, (status, result) => {
if (status === 'complete') {
console.log('路线规划完成:', result);
} else {
console.error('路线规划失败:', result);
}
});

// 清除路线
driving.clear();

其他路线规划插件:

场景插件
驾车AMap.Driving
公交AMap.Transfer
步行AMap.Walking
骑行AMap.Riding
货车AMap.TruckDriving

十、插件的按需加载

初始化时不确定会使用哪些功能,可以在业务触发后加载:

1
2
3
4
5
6
7
AMap.plugin(['AMap.Geocoder', 'AMap.PlaceSearch'], () => {
const geocoder = new AMap.Geocoder();
const placeSearch = new AMap.PlaceSearch({
city: '北京',
pageSize: 10,
});
});

同一页面不要混用 JS API 1.4.x 和 2.0,也不要同时用 Loader 与手写 <script> 重复加载 API。

十一、坐标系说明

高德地图使用 GCJ-02(火星坐标系)。常见数据源可能使用其他坐标系:

数据来源常见坐标系
GPS、国际通用经纬度WGS84
高德、腾讯、国内 GoogleGCJ-02
百度地图BD-09

如果标记整体偏移数百米,优先检查坐标系。不要只通过手动增减经纬度“校准”,应使用合规的坐标转换接口。经纬度数组的顺序是 [longitude, latitude],即 [经度, 纬度]

十二、常见问题排查

12.1 地图空白

依次检查:

  1. 容器是否有非零高度,创建地图时容器是否已挂载。
  2. 浏览器控制台和 Network 中是否有鉴权或资源加载错误。
  3. Key 是否为「Web 端(JS API)」类型,是否配置了正确域名。
  4. window._AMapSecurityConfig 是否在 Loader 或 JS API 之前设置。
  5. 页面 CSP、广告拦截器或公司代理是否拦截了高德资源。
  6. 弹窗或 Tab 从隐藏切换为显示后,是否调用了 map.resize()

12.2 INVALID_USER_KEY 或鉴权失败

  • Key 填写错误或已被删除、禁用;
  • Key 类型不匹配;
  • 域名白名单不包含当前域名;
  • Key 与安全密钥不属于同一个应用;
  • 安全配置执行得太晚;
  • 调用量或服务权限超出控制台限制。

12.3 AMap is not defined

不要在 AMapLoader.load() 完成之前使用 AMap

1
2
const AMap = await loadAMap();
const map = new AMap.Map(container);

12.4 Vue 路由切换后报错或内存上涨

组件卸载时销毁地图、Observer 和自行注册的监听:

1
2
3
4
5
6
onBeforeUnmount(() => {
resizeObserver?.disconnect();
map?.off('click', handleMapClick);
map?.destroy();
map = null;
});

12.5 定位失败

确认页面为 HTTPS、浏览器有位置权限,然后记录 statusresult.message 和完整错误对象。定位属于外部能力,产品上应允许用户手动选点或输入地址。

十三、上线检查清单

  • 使用 Web JS API 2.0,未混入旧版本脚本。
  • 开发、测试、生产 Key 已隔离。
  • Key 配置了最小范围的域名白名单。
  • 生产环境通过 serviceHost 代理保护安全密钥。
  • 地图容器有稳定尺寸,隐藏容器显示后会触发 resize()
  • Vue/React 组件卸载时销毁地图实例和监听器。
  • 用户输入的信息窗体内容已经防 XSS 处理。
  • 定位具备授权提示、失败提示和手动选址降级方案。
  • 已确认业务坐标系,检查经纬度顺序。
  • 已核对最新配额、计费、服务权限和合规要求。

十四、参考资料