☰
Vue中接入高德地图实时定位:密钥配置、坐标系偏移与watchPosition实战
2026/10/15 22:51:47 网站建设 项目流程

简介:vue集成高德地图实现用户实时定位的轻量代码包,面向需要在Vue项目中快速接入位置服务的前端开发者。压缩包仅2KB,虽小却包含两个核心文件:一个JS文件负责高德地图API的初始化、定位权限请求及经纬度获取,一个Vue组件则负责地图渲染、当前位置标记以及经纬度信息展示,整体封装简洁、开箱即用。该资源目前已吸引5144人浏览学习,在社区中具备较高参考热度。通过阅读这套代码,开发者可以掌握高德地图JS API与Vue生命周期结合的正确方式,理解getCurrentPosition定位调用、地图实例创建、标记点添加以及组件通信等关键环节;同时代码量精简,便于直接移植到自己的项目中,也能作为调试定位功能时的最小可复现示例,帮助快速解决实时显示用户位置的需求。

1. 一秒拿到用户位置,但地图上永远不对:Vue 里接高德实时定位到底难在哪

拿到经纬度简单,navigator.geolocation.getCurrentPosition一行就能返回坐标;真正让人纠结的是“拿到之后怎么办”:坐标系对不对、Marker 插在哪个图层、地图实例什么时候才不是 undefined、页面切后台后 watchPosition 还工不工作。我按自己在一个 Vue 2 老项目里接入高德地图实时定位的完整路径来写:申请 Web 端 Key、配置安全密钥、初始化地图、获取用户当前位置经纬度并打点,再到用 watchPosition 持续追踪的实时定位方案。适合正在做移动端 H5 打卡、配送轨迹、巡检签到这类需求的前端开发。文末把坐标偏移、权限、耗电这些黑匣子问题拆开讲,保证你照着做完能在高德地图上真看到用户位置,而不是看到人行道对面那个偏差了几十米的点。

2. 准备工作:密钥申请、Loader 选型与浏览器定位的边界

2.1 为什么我建议用官方 Loader 而不是 script 标签一把梭

早期接入高德 JS API 最常见的手法是直接在index.html里塞一个<script src="https://webapi.amap.com/maps?v=2.0&key=xxx">,全局挂一个AMap对象,Vue 里直接new AMap.Map()。这条路在小 demo 里跑得通,但到了工程化项目里很容易翻车:地图插件在页面脚本里的加载顺序不受你控制,AMap.Geolocation明明在 Network 面板里能看到,代码里new AMap.Geolocation()却报 “is not a constructor”。

所以我现在的习惯是用官方提供的 npm 包@amap/amap-jsapi-loader。它不是第三方封装,是高德自己维护的加载器,能解决版本锁定、插件加载顺序、按需加载三个实际问题。安装命令很简单:

npm install @amap/amap-jsapi-loader

安装后在组件里这样加载:

import AMapLoader from '@amap/amap-jsapi-loader'; AMapLoader.load({ key: '你的高德Web端Key', version: '2.0', plugins: ['AMap.Geolocation'], }) .then((AMap) => { console.log('AMap 加载成功,当前版本:', AMap.version); }) .catch((err) => { console.error('AMap 加载失败:', err); });

逻辑说明:load()方法返回 Promise,时序可控,不会在 Vue 渲染阶段卡住主流程;plugins数组明确声明本次要用到的插件,高德在加载时会把对应插件一并拉取并注册到 AMap 实例上。这也是我推荐它的核心原因——你不用再关心插件脚本到底注入成功没有。

参数说明里,key只能是高德开放平台里 “Web端(JS API)” 类型生成的 Key,不能用 Android/iOS 平台类型的 Key;version我建议锁死'2.0',不要用'1.4.x',因为 1.4 已经停止功能更新,且没有安全密钥机制,从合规和长期维护角度都不合适;plugins数组按需添加,用不到AMap.Driving就别写进去,免得增大首屏加载体积。这里有个小经验:如果项目里要多个组件共用同一个地图实例,把AMapLoader.load()的 Promise 缓存到模块级变量,避免每次路由进入都重新加载一遍脚本。

2.2 安全密钥与域名白名单:少配置一步就白屏半小时

JS API 2.0 上线后密钥机制变了,光有 Key 不够,还要配安全密钥。很多老项目的代码从 1.4 迁到 2.0 后地图白屏,十有八九是漏了这一步。高德官方给的前端配置方式是在加载地图之前设置一个全局变量:

window._AMapSecurityConfig = { securityJsCode: '你在高德控制台拿到的安全密钥', };

逻辑说明:JS API 2.0 在加载时会检查当前域名的白名单权限,同时校验安全密钥,两个条件一起满足才允许初始化地图。_AMapSecurityConfig是一个全局钩子,浏览器执行到它时高德内部脚本会读取并参与鉴权,所以必须放在AMapLoader.load()之前执行。我在实际项目里是把它放进index.html里地图脚本之前的一段内联脚本,保证时序上绝对靠前。

开发环境下有个常见困惑:本地localhost调试时明明没配白名单也能跑起来,于是上线前忘了把生产域名加进白名单,结果线上白屏。高德对localhost和127.0.0.1是放行的,但这里的“放行”不代表你可以跳过配置。确认名单时要把开发、测试、生产三个环境的域名都列进去,并且千万别把一个 Key 同时用在多个线上环境,防止某个环境域名泄漏后整把钥匙被刷。

如果你所在项目有安全评审,前端 Key 暴露在浏览器里这件事本身逃不掉,解决办法是把安全密钥放到后端接口下发,页面先拉配置接口再注入_AMapSecurityConfig。这种动态注入方式代码更啰嗦,但至少密钥不会直接躺在静态包里。注意,后端下发的安全密钥也属于敏感配置,不要让后端把它打到全量配置里,单独拆一个加密接口更稳。

2.3 原生 Geolocation 的三种误差来源:权限、协议与坐标系

正式写代码前,先把浏览器定位这件事的边界摸清楚。HTML5 的navigator.geolocation看起来无处不在,但真正落到生产环境,它有三个隐藏约束。第一是权限,用户一旦拒绝授权,没有任何 Web API 能再次强制唤起授权弹窗,只能引导用户到浏览器设置里手动打开,这个体验很糟糕但你只能接受。

第二是协议。Chrome、Safari 以及绝大多数现代浏览器只允许在 HTTPS 页面里调用定位接口,http://环境下调用会直接报错。开发时localhost是例外,但你只要打个内网 IP 的包放在局域网服务器上,定位大概率失效。因此项目一开始就该确认部署地址是 HTTPS,这个前置条件不满足,后面所有工作都白做。

第三是坐标系,这是最容易让开发者误判的一环。浏览器原生定位返回的国际坐标是 WGS-84,而高德地图内的坐标是 GCJ-02,两者在中国大陆地区存在几十米到几百米不等的偏移。如果你直接拿原生经纬度丢给高德 Marker,定位点会落在马路对面或者隔壁小区,第一次遇到时很容易误判成精度问题,调了半天才发现是坐标基准不一致。

解决路径有三条。第一条最省事:用高德自己的AMap.Geolocation插件,它内部会把定位结果转成 GCJ-02,直接塞给 Marker 就行,这也是我在下文中主力采用的方式。第二条是保留原生定位,但要把 WGS-84 坐标发到后端,用高德 Web 服务里的坐标转换接口转成 GCJ-02 再回传。第三条是前端引入转换算法库自己算,但民间算法在高纬度或边界地区偶尔有偏差,我不建议核心定位功能走这条路。最佳实践是:能用高德定位组件就用高德定位组件,少给自己造轮子。

3. 把当前经纬度钉在高德地图上:Vue 组件里的最小可用实现

3.1 初始化地图:容器高度、挂载时机与 map 实例管理

Vue 组件里初始化高德地图,最常见的问题不是代码语法错,而是容器没有高度。new AMap.Map('container')内部读取容器宽高,如果读到 0,地图初始化就完成在一个不可见的画布上,表现为页面一片空白。我见过的翻车案例里,至少一半是 CSS 忘了写高度,另一半是写在created生命周期里,这时候 DOM 还没渲染完,选择器拿不到元素。

正确的做法是把初始化放在mounted,并且给容器一个明确高度。这不仅是代码习惯,也是地图类 SDK 的共同要求。下面是这一章要用的基础模板,它把一个相对完整的初始化流程放在一起:

<template> <div id="mapContainer" class="map-container"></div> </template> <script> import AMapLoader from '@amap/amap-jsapi-loader'; export default { name: 'CurrentLocationMap', data() { return { map: null, marker: null, }; }, mounted() { this.initMap(); }, methods: { async initMap() { try { const AMap = await AMapLoader.load({ key: '你的高德Web端Key', version: '2.0', plugins: ['AMap.Geolocation'], }); this.map = new AMap.Map('mapContainer', { zoom: 16, viewMode: '2D', }); } catch (err) { console.error('地图初始化失败:', err); } }, }, }; </script> <style scoped> .map-container { width: 100%; height: 100vh; } </style>

逻辑说明:AMapLoader.load()是异步的,所以initMap用了async/await;this.map存到组件实例上,后续所有地图操作都通过它完成。viewMode: '2D'表示二维地图,如果用'3D'会启用倾斜视角,后续视野跟随和旋转逻辑都会变复杂,定位场景用 2D 就够了。

参数说明:zoom: 16是市级街道级别,适合显示用户当前位置;如果场景是园区内做巡检,可以放大到 17 或 18;如果功能入口是城市级切换,则建议zoom: 11起步,让用户先看到整体再定位。map-container的高度我写的是100vh,实际项目中通常由布局决定,你只需要保证它不为 0。

这里有个容易被忽略的细节:如果父级容器使用了display: none或v-if控制显示,地图初始化时容器不可见,之后即使v-show显示出来,地图底图也常常缺失。所以,当地图所在的 Tab 面板是动态切换时,必须等面板可见后再调initMap(),或者面板切换后调用地图实例的resize方法。Vue 2 项目里可以用$nextTick保证容器已渲染,再初始化。

3.2 一次性定位:用 AMap.Geolocation 拿到当前经纬度

日常业务里有些场景只需要定位一次,比如用户进入页面时把当前位置打在地图上,后续不跟踪。这时候用AMap.Geolocation的getCurrentPosition就够,不需要一直开着手电筒一样耗电的持续监听。

我在基础模板上补一个locateOnce方法:

async initMap() { try { const AMap = await AMapLoader.load({ key: '你的高德Web端Key', version: '2.0', plugins: ['AMap.Geolocation'], }); this.map = new AMap.Map('mapContainer', { zoom: 16, viewMode: '2D', }); this.locateOnce(AMap); } catch (err) { console.error('地图初始化失败:', err); } }, locateOnce(AMap) { const geolocation = new AMap.Geolocation({ enableHighAccuracy: true, timeout: 10000, maximumAge: 0, }); this.map.addControl(geolocation); geolocation.getCurrentPosition((status, result) => { if (status === 'complete') { const { position, accuracy } = result; console.log('当前定位精度:', accuracy); this.renderMarker(position); } else { console.error('定位失败:', result.message); // 这里可以接入 UI 层面的 toast 提示 } }); }, renderMarker(lnglat) { if (!this.marker) { this.marker = new AMap.Marker({ position: lnglat, map: this.map, }); } this.map.setCenter(lnglat); }

逻辑说明:locateOnce接收AMap实例作为参数,在内部创建AMap.Geolocation定位插件实例,然后用getCurrentPosition发起一次定位。回调里先判断status是不是'complete',只有完整定位成功时才去渲染标记,避免把错误状态当作坐标写入地图。renderMarker里先判断this.marker是否存在,存在就复用,不存在才新建,这个模式后面实时追踪时还会沿用。

参数说明:enableHighAccuracy: true意味着优先使用 GPS 或高精度网络定位,手机上会明显增加功耗,但对单次定位来说无所谓;timeout: 10000是定位超时时间 10 秒,超过这个时间回调会进入 error 分支;maximumAge: 0表示不接受任何缓存坐标,每次都要新鲜定位结果,对于打卡这种对时间敏感的场景是必要的。注意,map.addControl(geolocation)这行代码会在地图右上角添加一个高德自带的“定位”按钮,用户点击它也能触发定位,项目中如果不需要这个按钮可以不调用addControl,但不调它时getCurrentPosition依然可用。

3.3 Marker 打点与视口跟随:把“具体位置”落到 UI 上

定位拿到经纬度后,地图上要能明确告诉用户“你在这里”。我这里的做法是用AMap.Marker在地图坐标上生成一个标记点。renderMarker里这行new AMap.Marker({ position: lnglat, map: this.map })是最简形态,Marker 样式是高德默认的蓝色气泡,在绝大多数场景里够用。

如果产品要求一个更明显的定位样式,比如中心带圆点的定位图标,可以通过content字段传入自定义 HTML 字符串:

this.marker = new AMap.Marker({ position: lnglat, map: this.map, content: ` <div class="custom-location-marker"> <div class="pulse"></div> <div class="dot"></div> </div> `, });

逻辑说明:content可以是任意 HTML 字符串,高德会把这段 HTML 插入到 Marker 对应的 DOM 容器里,替代默认渲染内容。配合 CSS 动画可以做出呼吸灯或者脉冲点效果,用来强调“当前位置”。注意,使用自定义 content 之后,Marker 的锚点位置默认在容器的左上角,如果视觉上出现偏差,需要给offset属性配置一个AMap.Pixel(-x, -y)来修正锚点,让圆心对准经纬度坐标。

视口跟随这行代码this.map.setCenter(lnglat)的意义是把地图视野中心移动到用户位置。没有它,定位坐标可能在地图视野外,用户看不到任何变化。定位成功后通常还需要this.map.setZoom(16)或让用户手动缩放,我一般只在首次定位时强制设置缩放等级,之后把缩放权交还给用户,避免持续追踪时每次更新都改变缩放级别,那样会让用户没法查看地图的其他区域。

一个关于生命周期的小提醒:如果用户在定位还没返回时就离开了页面,回调依然会执行并操作this.map,此时组件可能已经销毁,地图实例内部会报错。我通常会在beforeDestroy里做清理,下一章讲实时定位时会同步给出完整的资源释放代码。

4. 实时跟踪用户位置:watchPosition 与精度过滤的取舍

4.1 一次定位与持续定位的区别:回调时机和生命周期不一样

getCurrentPosition是一次性操作,回调触发完就结束了;实时定位场景比如配送轨迹、路线记录需要的则是持续回调。高德定位插件里对应的方法是watchPosition,写法上很像,但语义完全不同:

startWatch() { this.geolocation = new AMap.Geolocation({ enableHighAccuracy: true, timeout: 10000, maximumAge: 0, }); this.geolocation.watchPosition((status, result) => { if (status !== 'complete') return; const { position, accuracy } = result; this.handlePosition(position, accuracy); }); }, handlePosition(position, accuracy) { if (accuracy > 100) { console.warn(`定位精度 ${accuracy} 米,超过阈值,跳过本次更新`); return; } this.renderMarker(position); }

逻辑说明:watchPosition内部会根据定位源变化持续回调,包括 GPS 更新、基站切换、WiFi 变化都会触发。回调里先做状态判断,再读position和accuracy。accuracy单位是米,数值越小定位越准。我在handlePosition里加了 100 米的阈值过滤,作用是丢弃明显不可用的定位结果,避免 UI 上的标记点在城市间乱跳。

这和getCurrentPosition的最大区别在于,你不会知道下一次回调什么时候来,也不知道它会不会来。所以不能假设回调是等间隔触发的,后续的节流逻辑也要建立在“触发时间不确定”这个前提上。

4.2 标记更新与视口跟随:避免每次回调都重建 Marker

继续追踪时最忌讳的做法是每次回调都new AMap.Marker()。频繁销毁和重建 DOM 节点不仅消耗性能,还会导致标记点闪烁、地图卡顿。必要的做法是只在第一次创建 Markers,之后调用setPosition更新坐标即可:

renderMarker(lnglat) { if (!this.marker) { this.marker = new AMap.Marker({ position: lnglat, map: this.map, }); } else { this.marker.setPosition(lnglat); } this.map.setCenter(lnglat); }

逻辑说明:marker实例在组件生命周期内只创建一次,后续坐标更新全部通过setPosition完成。setPosition接收LngLat对象或者[lng, lat]数组,这里直接传入高德定位插件返回的position对象,它本身是LngLat实例,所以不需要额外处理。

在这个基础上,轨迹场景可以再扩展:如果要在移动过程中画出路线,把每次定位得到的经纬度存入数组,然后用AMap.Polyline把点连起来。这里的存储数组需要注意长度控制,记录一天的数据会撑爆内存,通常做法是每攒够一定点数就给后端回传一次,然后清空本地数组。

4.3 精度过滤与节流:避免一个回调把页面拖垮

watchPosition有频率上限,但在 GPS 信号不稳定的场景下,回调可能短时间内连续触发多次,每次又带来一个新坐标。如果这些坐标直接写入 UI 并回传后端,先是地图上的标记点抖成筛子,然后是用户流量狂飙,最后后端接口被刷到报警。

我的方案是把“精度过滤”和“距离节流”双管齐下。精度过滤已经在handlePosition里做了一部分,接下来补上距离节流:

handlePosition(position, accuracy) { if (accuracy > 100) return; const nextLng = position.lng; const nextLat = position.lat; const lastLng = this.lastPosition ? this.lastPosition[0] : null; const lastLat = this.lastPosition ? this.lastPosition[1] : null; if (lastLng !== null && lastLat !== null) { const latDelta = Math.abs(nextLat - lastLat) * 111000; const lngDelta = Math.abs(nextLng - lastLng) * 111000 * Math.cos((nextLat * Math.PI) / 180); const distance = Math.sqrt(latDelta * latDelta + lngDelta * lngDelta); if (distance < 5) { return; } } this.lastPosition = [nextLng, nextLat]; this.renderMarker(position); }

逻辑说明:这段代码用经纬度差值估算实际移动距离。纬度方向每度约 111 公里,经度方向要乘上当前纬度的余弦修正,所以在高纬度地区东西方向的移动计算才更准。如果估算距离小于 5 米,就认为用户没有实际位移,只是定位源抖动,直接丢弃这次回调。只有位移超过 5 米才更新页面上的 Markers。

参数说明里有两个地方可以按业务调整。第一是 5 米的判断阈值,如果你做的是室内定位演示,可以缩到 2 米;如果你做的是城际轨迹回放,可以放大到 20 米。第二是精度过滤的 100 米阈值,室内定位时 GPS 信号被遮挡,精度经常大于 100 米,如果业务强依赖室内连续轨迹,这个阈值应该放宽,同时降低更新的频率。两个参数是配合使用的,精度阈值放宽,距离阈值就要收紧,否则一堆低精度坐标叠加在轨迹上,画出来的路径能上演摩西分海。

5. Vue + 高德实时定位避坑:5 个现象、原因与解决路径

5.1 地图白屏:安全密钥、白名单和容器高度哪个在作怪

现象:页面加载完成,周围 UI 正常,地图区域是空白,控制台偶尔能看到ERROR: 401或AMap is not defined之类的报错。

原因排查顺序很有讲究,我每次都是按三张牌顺序排查。第一张牌是安全密钥,2.0 版本下没有设置window._AMapSecurityConfig,地图脚本会加载失败;第二张牌是域名白名单,网络请求能发出,但服务端拒绝了,响应里会带上具体的权限信息;第三张牌才是容器高度,如果网络正常但地图区域高度为 0,地图内部绘制不了画布,同样表现为白屏。

解决路径:先打开 Network 面板过滤地图相关请求,看返回状态码是不是 401/403。401 多半是密钥问题,403 多数是白名单没匹配;如果没有异常请求但地图还是空白,则在初始化后立刻打印map.getSize(),如果输出{width: 0, height: 0},问题就在容器尺寸上。容器高度修复后,再调用一次this.map.resize()。这条路径我走了几十遍,准确率接近百发百中。

5.2 定位回调一直不触发:不是代码问题,是协议和用户设置问题

现象:地图能正常显示,定位功能也调用了,但watchPosition或getCurrentPosition的回调迟迟不来,也没有报错。

原因:最常见的是部署地址不是 HTTPS,浏览器直接把定位能力禁掉了。第二常见的是用户之前拒绝过定位授权,浏览器记住了这个选择,之后每次访问都不再询问,也不会执行定位逻辑。第三常见是手机系统的省电模式或者权限管理里针对该浏览器禁用了定位,这在 Android 上尤其频繁,用户可能自己都不知道什么时候关过。

解决路径:第一步在开发者工具里手动审查一下地址栏,http://就直接换 HTTPS 再试。第二步通过浏览器设置检查权限状态,如果权限是“已拒绝”,需要用户手动改回“允许”,代码层面无能为力。第三步在前端做个可感知的提示:定位失败且有权限相关错误时,引导用户去系统设置调整,而不是干巴巴地报错。不要反复调用定位接口,后台被浏览器限流后回调更慢。

5.3 定位点偏移几十米甚至一条街:坐标系的坑

现象:地图上确实有标记点,定位也显示成功,但标记点落在距离真实位置几十米开外,可能在马路对面,也可能在隔壁小区里。

原因:绝大多数情况是代码用了浏览器原生navigator.geolocation返回的 WGS-84 坐标,没有做坐标系转换就传给高德。高德地图使用 GCJ-02,两者有系统性偏移。少数情况是高德定位组件的getCurrentPosition返回了精度极差的定位结果,比如在室内多个基站信号下定位精度只有 200 米。

解决路径:优先确认定位来源。用AMap.Geolocation定位就不会有坐标基准错的问题,它内部已经帮你转换好了。如果核心场景必须用原生定位,那就得转坐标系,最常见的做法是把 WGS-84 坐标通过高德 Web 服务里的坐标转换接口转成 GCJ-02,再回填到地图上。注意不要用道听途说来的算法公式硬转,有些算法在特定区域偏差更大。如果已经用了高德插件还是有偏移,检查定位回调里的accuracy值,大于 100 的结果直接丢弃,宁可位置不更新也比画错地方强。

5.4 watchPosition 高频回调带来发热和耗电:参数与节流要一起调

现象:手机在持续定位页面里明显发热,电量掉得飞快,后台接口请求频率高得离谱,一天下来流量消耗也惊人。

原因:enableHighAccuracy: true会一直尝试唤醒 GPS 模块,maximumAge: 0又让每一次回调都拿全新坐标,两个参数叠加就是把手机定位能力拉满的运营状态。再加上handlePosition里没有节流,每次回调都上报后端,网络与定位全都在工作,发热掉电是必然。

解决路径:把enableHighAccuracy改成false,用基站加 WiFi 定位,精度从十几米降到几十米,但对大多数业务场景够用。maximumAge改成一个正常值,比如30000,30 秒内复用缓存坐标。同时加节流逻辑:每 5 秒至少间隔一次,再增加距离门槛,小于 10 米的上报直接丢弃。如果业务允许,把高频实时定位退化成“每 30 秒定时获取一次位置”是最省电的方案,毕竟不是所有场景都需要逐米级轨迹。

5.5 页面隐藏或组件销毁后,定位回调还在跑:清理资源不能偷懒

现象:用户把浏览器切到后台,控制台还在打日志;路由已经从定位页跳走,却还在上报坐标;组件销毁后偶尔还能看到来自地图回调的报错。

原因:watchPosition一旦开启,就不会因为页面隐藏或者组件卸载自动停止。浏览器对后台页面的定时器有节流限制,但定位回调在很多情况下仍在继续,尤其是移动端浏览器切后台后某些定位源仍会回调。组件销毁后,回调里访问this.marker或this.map,对象已经销毁,自然报错。

解决路径:在组件里监听visibilitychange事件,页面隐藏时主动清理,回到页面时重新开启:

mounted() { document.addEventListener('visibilitychange', this.handleVisibility); }, beforeDestroy() { document.removeEventListener('visibilitychange', this.handleVisibility); if (this.geolocation) { this.geolocation.clearWatch(); } }, methods: { handleVisibility() { if (document.hidden) { if (this.geolocation) { this.geolocation.clearWatch(); } } else if (this.isTracking) { this.startWatch(); } } }

逻辑说明:visibilitychange在页面切换到后台或恢复前台时触发,document.hidden为 true 表示页面不可见。隐藏时调用clearWatch()停掉定位监听,回到前台时再重新startWatch()。最关键的是beforeDestroy里的兜底清理,不管页面是怎么离开的,组件销毁前都必须把定位监听停掉。这行代码是我踩过坑之后养成的肌肉记忆,少了它,页面迟早会被那些“幽灵回调”折腾到卡顿。

6. 把定位能力封装成 useLocation 并加上业务级更新策略

6.1 抽出组合式 API:Vue 3 的 useLocation 封装

项目里如果有多个页面都需要实时定位,把定位逻辑直接写在组件里,后面维护时就要在几个文件里重复改参数。常见的做法是抽取成一个独立的组合式函数,让组件只关心业务,不关心定位底层。Vue 3 里这个模式写起来非常自然:

import { ref, onUnmounted } from 'vue'; import AMapLoader from '@amap/amap-jsapi-loader'; let cachedAMap = null; async function getAMapInstance() { if (cachedAMap) return cachedAMap; cachedAMap = await AMapLoader.load({ key: '你的高德Web端Key', version: '2.0', plugins: ['AMap.Geolocation'], }); return cachedAMap; } export function useLocation() { const lng = ref(null); const lat = ref(null); const accuracy = ref(null); let geolocation = null; async function start() { const AMap = await getAMapInstance(); geolocation = new AMap.Geolocation({ enableHighAccuracy: true, timeout: 10000, maximumAge: 0, }); geolocation.watchPosition((status, result) => { if (status === 'complete') { lng.value = result.position.lng; lat.value = result.position.lat; accuracy.value = result.accuracy; } }); } function stop() { if (geolocation) { geolocation.clearWatch(); geolocation = null; } } onUnmounted(stop); return { lng, lat, accuracy, start, stop, }; }

逻辑说明:getAMapInstance把加载地图脚本的 Promise 缓存到模块级变量,多次调用不会重复发起脚本请求。useLocation内部维护经纬度相关的 ref,watchPosition每次回调把新的经纬度和精度写入 ref,组件通过watch监听变化来更新地图上的 Markers。组件销毁时自动调用stop(),不会再出现泄漏问题。

6.2 业务侧组合:让 Markers 只移动到合理距离

组合式函数返回的是响应式坐标,组件里怎么消费,由业务自己定。比如后台打卡场景,可以只在坐标更新时打一条日志;配送轨迹场景,则要把坐标写入轨迹数组。下面是在组件里把响应式坐标和地图 Markers 联动的最小片段:

const { lng, lat, start, stop } = useLocation(); watch([lng, lat], ([newLng, newLat]) => { const newPosition = [newLng, newLat]; if (!marker.value) { marker.value = new AMap.Marker({ position: newPosition, map: map.value, }); } else { marker.value.setPosition(newPosition); } map.value.setCenter(newPosition); });

逻辑说明:watch监听两个 ref 的变化,只要定位回调写入新坐标,就会更新地图。Marker 的创建逻辑和之前一致,首次创建,后续复用。当多个定时器、多个业务模块都依赖定位坐标时,统一从useLocation里取数,比在组件里直接操作原生定位接口要可控得多。

最后一层业务优化是我在项目里逐步养成的习惯:在stop()之外,增加一个“活动不更新”的自动暂停机制。页面可见且用户在前台时,正常更新坐标;页面切后台超过 5 分钟,自动stop(),等回到前台再重新start()。这个策略能让定位模块的耗电降到原来的十分之一以下,对长期挂在后台的 H5 页面尤其重要。顺手记一句血泪经验:实时定位从来不是“把 API 接上就行了”的事,精度、频率、清理、节流,每一个都要结合业务想清楚。这节内容要是能让你少走一段弯路,那最好不过,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询