或者用yarn
Vue3项目里怎么快速接入并深度用高德地图?踩过的坑得提前知道吗?
选这个话题的原因
最近接手了几个本地生活、房产中介的私活儿,全部要求用Vue3+TypeScript做,还必须嵌高德地图——找房要精准定位小区位置、显示周边学校地铁;探店要规划路线、给商家标自定义图标,一开始以为只是套个SDK就行,结果踩了一堆坑:TypeScript类型飘红飘得像过年贴对联、组件销毁后地图还占内存、自定义InfoWindow点不动弹,后来翻了好多官方文档、社区经验贴,结合自己的调试,总结出了一套还算顺手的接入+避坑方案,今天就分享给大家,希望能帮到正在开发地图相关功能的小伙伴。
第一步:申请高德地图开发者账号和Key,这个是基础
不管是做PC端还是移动端Web,高德地图都得要个合法的身份凭证,也就是Key,具体怎么申请呢?很简单,先去官方平台注册账号,然后进入控制台创建应用,应用类型选「Web端(JS API)」,提交后就能拿到Key了。
这里有个小细节必须注意:创建Key的时候,要设置「服务域名白名单」或者「IP白名单」,本地开发的话,建议把localhost加上端口号(比如localhost:3000、localhost:5173),上线前一定要换成真实的生产域名,不然地图加载不出来,或者只能显示灰色的底图,不同的API需要不同的权限,比如想做逆地理编码、路径规划,得在控制台的「应用管理」→「我的应用」→「添加服务」里勾选对应的服务,不然调用API的时候会报错403或者权限不足。
第二步:Vue3接入高德地图的两种主流方式,按需选就行
现在Vue3开发大多用Vite或者Webpack打包,接入高德地图的方式主要有两种:一种是官方推荐的「JSAPI Loader」动态加载,另一种是在index.html里直接引入script标签,两种方式各有优缺点,大家可以根据项目需求选择。
JSAPI Loader动态加载(更推荐,尤其是TS项目)
动态加载的好处是不会阻塞页面渲染,只有当需要用到地图的组件加载时才会去下载SDK,能显著提升首屏加载速度,而且官方给Loader写了专门的TypeScript类型声明,只要安装了@amap/amap-jsapi-loader和@types/amap-jsapi-loader,TS类型就不会飘红(后期用地图组件的时候可能还需要补充少量自定义类型,这个后面会讲)。
具体怎么用呢?首先打开终端,在Vue3项目根目录下安装依赖:
npm install @amap/amap-jsapi-loaderyarn add @amap/amap-jsapi-loader # 如果是TS项目,记得装类型声明 npm install @types/amap-jsapi-loader -D # yarn的话 yarn add @types/amap/amap-jsapi-loader -D
然后创建一个封装好的地图组件,比如components/Amap.vue,在组件的onMounted钩子函数里用Loader加载SDK,加载完成后再初始化地图:
<template>
<div ref="mapContainer" class="map-container"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import AMapLoader from '@amap/amap-jsapi-loader'
// 地图容器ref
const mapContainer = ref<HTMLDivElement | null>(null)
// 保存地图实例,方便后续销毁
let mapInstance: AMap.Map | null = null
onMounted(async () => {
try {
// 动态加载高德地图SDK
const AMap = await AMapLoader.load({
key: '你的Web端JS API Key', // 替换成自己申请的Key
version: '2.0', // 建议用最新的2.0版本,功能更全,性能更好
plugins: ['AMap.Geolocation', 'AMap.Marker', 'AMap.InfoWindow'], // 这里可以按需加载插件
})
// 初始化地图
if (mapContainer.value) {
mapInstance = new AMap.Map(mapContainer.value, {
zoom: 14, // 初始缩放级别,数字越大越清楚
center: [116.397428, 39.90923], // 初始中心点,这里是天安门
mapStyle: 'amap://styles/whitesmoke', // 自定义地图样式,官方有很多免费模板
viewMode: '3D', // 开启3D模式,加个pitch属性就能看立体建筑了
pitch: 30, // 3D模式下的俯仰角
})
// 加载定位插件,获取用户当前位置
const geolocation = new AMap.Geolocation({
enableHighAccuracy: true, // 开启高精度定位
timeout: 10000, // 定位超时时间,单位毫秒
zoomToAccuracy: true, // 定位成功后自动缩放地图到合适级别
})
mapInstance.addControl(geolocation)
geolocation.getCurrentPosition((status, result) => {
if (status === 'complete') {
console.log('定位成功', result)
// 定位成功后可以把中心点移到用户当前位置
mapInstance?.setCenter([result.position.lng, result.position.lat])
} else {
console.log('定位失败', result)
}
})
}
} catch (error) {
console.error('地图加载失败', error)
}
})
// 组件销毁时记得销毁地图实例,释放内存
onUnmounted(() => {
if (mapInstance) {
mapInstance.destroy()
mapInstance = null
}
})
</script>
<style scoped>
.map-container {
width: 100%;
height: 600px; /* 必须给地图容器设置高度,不然地图显示不出来 */
}
</style>
这样一个基础的地图组件就封装好了,直接在需要用的页面里引入就行。
index.html直接引入script标签(适合简单项目,不用额外安装依赖)
如果项目只是需要一个简单的地图展示,不需要太复杂的功能,也不需要TypeScript,那可以直接在index.html的head或者body里引入高德地图的script标签:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- 引入高德地图SDK -->
<script type="text/javascript">
window._AMapSecurityConfig = {
securityJsCode: '你的安全密钥', // 这个和Key是配套的,申请Key的时候会自动生成
}
</script>
<script src="https://webapi.amap.com/maps?v=2.0&key=你的Web端JS API Key"></script>Vue3高德地图</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
引入之后,直接在Vue组件的onMounted钩子函数里用window.AMap初始化地图就行,不过这种方式有个缺点:SDK会在页面加载的时候就下载,会阻塞首屏渲染;而且如果是TS项目,没有官方的类型声明,需要自己补充window.AMap的类型,比较麻烦,所以除非是非常简单的项目,否则还是建议用JSAPI Loader动态加载。
第三步:深度使用高德地图的几个常用功能
基础的地图展示和定位搞定了,接下来讲讲几个私活儿里经常用到的功能:自定义Marker、自定义InfoWindow、路径规划。
自定义Marker
官方默认的Marker是一个蓝色的小水滴,实在是太丑了,接私活儿的时候客户一般都会要求换成自己的图标,自定义Marker很简单,只需要在初始化Marker的时候设置icon属性就行:
// 在onMounted钩子函数里,地图初始化之后添加自定义Marker
// 首先创建一个自定义图标
const customIcon = new AMap.Icon({
image: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png', // 自定义图标的地址,可以是本地图片,也可以是网络图片
size: new AMap.Size(32, 32), // 图标尺寸
imageSize: new AMap.Size(32, 32), // 图标显示尺寸,和size一样的话就是原尺寸显示
anchor: new AMap.Pixel(16, 32), // 图标锚点,也就是图标底部中心点对应地图上的经纬度
})
// 然后创建Marker
const marker = new AMap.Marker({
position: [116.397428, 39.90923], // Marker的位置
icon: customIcon, // 使用自定义图标 '天安门', // 鼠标悬停时显示的文字
draggable: true, // 是否可拖拽
})
// 把Marker添加到地图上
mapInstance?.add(marker)
这里有个小技巧:如果有多个不同的Marker,比如餐厅用红色图标、酒店用蓝色图标,可以创建一个图标库,根据数据的类型动态选择对应的图标。
自定义InfoWindow
官方默认的InfoWindow样式也很一般,接私活儿的时候客户一般都会要求换成自己设计的样式,自定义InfoWindow有两种方式:一种是设置content属性为HTML字符串,另一种是设置content属性为DOM元素,两种方式都可以,大家可以根据自己的喜好选择。
我个人更推荐用DOM元素的方式,因为Vue3的响应式数据可以直接绑定到DOM元素上,修改数据的时候InfoWindow的内容会自动更新,不用手动刷新,具体怎么用呢?
// 首先在template里添加一个隐藏的InfoWindow内容容器
<template>
<div ref="mapContainer" class="map-container"></div>
<div ref="infoWindowContent" class="info-window-content" style="display: none;">
<h3>{{ currentMarkerTitle }}</h3>
<p>地址:{{ currentMarkerAddress }}</p>
<button @click="handleButtonClick">查看详情</button>
</div>
</template>
<script setup lang="ts">
// 新增的ref和响应式数据
const infoWindowContent = ref<HTMLDivElement | null>(null)
const currentMarkerTitle = ref('')
const currentMarkerAddress = ref('')
// 保存InfoWindow实例
let infoWindowInstance: AMap.InfoWindow | null = null
// 在onMounted钩子函数里,地图初始化之后添加InfoWindow
infoWindowInstance = new AMap.InfoWindow({
isCustom: true, // 开启自定义样式
content: infoWindowContent.value as HTMLDivElement, // 使用DOM元素作为内容
offset: new AMap.Pixel(0, -32), // InfoWindow的偏移量,避免被Marker挡住
})
// 给Marker添加点击事件,点击时打开InfoWindow
marker.on('click', () => {
// 更新响应式数据
currentMarkerTitle.value = '天安门广场'
currentMarkerAddress.value = '北京市东城区景山前街4号'
// 打开InfoWindow
infoWindowInstance?.open(mapInstance as AMap.Map, marker.getPosition())
})
// 按钮点击事件
const handleButtonClick = () => {
console.log('点击了查看详情按钮')
// 这里可以跳转到详情页,或者打开弹窗
}
</script>
<style scoped>
/* 自定义InfoWindow的样式,注意不要加scoped,因为InfoWindow是添加到地图容器外面的DOM */
</style>
<style>
/* 全局样式,用来修改InfoWindow的背景、边框等 */
.amap-info-content {
background-color: white !important;
border-radius: 8px !important;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1) !important;
padding: 16px !important;
border: none !important;
}
/* 隐藏默认的InfoWindow箭头 */
.amap-info-sharp {
display: none !important;
}
/* 自定义InfoWindow内容的样式 */
.info-window-content {
display: block !important; /* 覆盖template里的display: none */
}
.info-window-content h3 {
margin: 0 0 8px 0;
font-size: 16px;
color: #333;
}
.info-window-content p {
margin: 0 0 12px 0;
font-size: 14px;
color: #666;
}
.info-window-content button {
padding: 6px 12px;
background-color: #007AFF;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
</style>
这里要注意两个坑:第一个坑是InfoWindow的content如果是DOM元素,一定要在InfoWindow初始化之前创建好DOM元素,而且DOM元素不能加v-if,只能用v-show或者style="display: none;",不然InfoWindow会找不到content;第二个坑是InfoWindow是添加到地图容器外面的DOM,所以修改样式的时候不能加scoped,否则样式不会生效。
路径规划
路径规划也是本地生活、房产中介项目里经常用到的功能,比如从用户当前位置到目的地的步行、骑行、驾车路线,高德地图的路径规划功能需要加载对应的插件,比如步行路径规划需要加载AMap.Walking,驾车路径规划需要加载AMap.Driving,骑行路径规划需要加载AMap.Riding,公交路径规划需要加载AMap.Transfer。
我以驾车路径规划为例,给大家演示一下怎么用:
// 首先在JSAPI Loader.load的plugins数组里添加AMap.Driving
plugins: ['AMap.Geolocation', 'AMap.Marker', 'AMap.InfoWindow', 'AMap.Driving'],
// 然后在onMounted钩子函数里,地图初始化之后添加驾车路径规划控件
const driving = new AMap.Driving({
map: mapInstance as AMap.Map, // 把路径规划结果显示在地图上
panel: 'driving-panel', // 把路径规划的文字说明显示在指定的DOM元素里
hideMarkers: false, // 是否隐藏起点和终点的Marker
showTraffic: true, // 是否显示实时路况
})
// 规划从天安门到故宫博物院的驾车路线
driving.search(
[
{keyword: '天安门广场', city: '北京'}, // 起点,可以是经纬度,也可以是关键词
{keyword: '故宫博物院', city: '北京'}, // 终点
],
(status, result) => {
if (status === 'complete') {
console.log('路径规划成功', result)
} else {
console.log('路径规划失败', result)
}
}
)
最后在template里添加路径规划文字说明的DOM元素:
<template>
<div ref="mapContainer" class="map-container"></div>
<div id="driving-panel" class="driving-panel"></div>
</template>
<style scoped>
.driving-panel {
position: absolute;
top: 10px;
right: 10px;
width: 300px;
max-height: 500px;
overflow-y: auto;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
padding: 16px;
}
</style>
这样路径规划功能就搞定了,是不是很简单?
第四步:避坑指南,这些坑我都踩过
前面讲了接入和常用功能,接下来讲讲我踩过的几个大坑,大家一定要提前注意,不然会浪费很多时间。
坑一:TypeScript类型飘红
虽然官方给Loader写了类型声明,但地图组件的类型声明还是不全的,比如AMap.Icon的imageSize属性、AMap.InfoWindow的isCustom属性,有时候会飘红,这时候怎么办呢?可以自己补充一个全局的类型声明文件,比如src/types/amap.d.ts:
// src/types/amap.d.ts
declare namespace AMap {
interface IconOptions {
imageSize?: Size;
}
interface InfoWindowOptions {
isCustom?: boolean;
}
}
这样类型就不会飘红了,如果还有其他属性飘红,可以查官方文档,然后自己补充到类型声明文件里。
坑二:组件销毁后地图还占内存
这个坑很容易被忽略,如果页面频繁切换包含地图的组件,会导致内存泄漏,页面越来越卡,甚至会崩溃,所以一定要在组件的onUnmounted钩子函数里销毁地图实例,释放内存:
onUnmounted(() => {
if (mapInstance) {
mapInstance.destroy()
mapInstance = null
}
// 如果有InfoWindow、Marker、路径规划控件等,也要记得销毁
if (infoWindowInstance) {
infoWindowInstance.close()
infoWindowInstance = null
}
if (marker) {
mapInstance?.remove(marker)
// 如果是数组形式的Marker,可以用mapInstance?.remove(markers)
}
if (driving) {
driving.clear()
driving = null
}
})
坑三:自定义InfoWindow的内容没有响应式
前面讲了用DOM元素的方式实现自定义InfoWindow,这样Vue3的响应式数据可以直接绑定到DOM元素上,但有时候会发现修改响应式数据后,InfoWindow的内容没有自动更新,这时候怎么办呢?可以在修改响应式数据之后,手动调用一下InfoWindow的setContent方法:
// 给Marker添加点击事件,点击时打开InfoWindow
marker.on('click', () => {
// 更新响应式数据
currentMarkerTitle.value = '天安门广场'
currentMarkerAddress.value = '北京市东城区景山前街4号'
// 手动更新InfoWindow的内容
infoWindowInstance?.setContent(infoWindowContent.value as HTMLDivElement)
// 打开InfoWindow
infoWindowInstance?.open(mapInstance as AMap.Map, marker.getPosition())
})
这样InfoWindow的内容就会自动更新了。
坑四:Key或安全密钥配置错误
如果Key配置错误,地图会显示灰色的底图,或者加载不出来,控制台会报403或者权限不足的错误,这时候一定要检查Key是否正确,是否是Web端JS API的Key,是否设置了正确的服务域名白名单或者IP白名单,从2021年12月02日起,新申请的Web端JS API Key必须配合安全密钥使用,安全密钥要在引入SDK之前设置在window._AMapSecurityConfig里,不然地图也会加载不出来。
就是Vue3项目里快速接入并深度用高德地图的全部内容,包括申请Key、两种接入方式、常用功能、避坑指南。
- 申请Key的时候要设置服务域名白名单或IP白名单,需要的话还要添加对应的服务权限;
- 推荐用JSAPI Loader动态加载SDK,尤其是TS项目;
- 封装地图组件的时候要在onUnmounted钩子函数里销毁地图实例和相关控件,释放内存;
- 自定义InfoWindow的时候要用DOM元素的方式,修改响应式数据后要手动调用setContent方法;
- Key或安全密钥配置错误会导致地图加载不出来,一定要仔细检查。
希望这篇文章能帮到正在开发地图相关功能的小伙伴,如果大家还有其他问题,欢迎在评论区留言讨论。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


