一个前端程序猿,在最近的开发过程中遇到了这样的需求:需要获取用户的当前位置,但是由于后端有现成的将经纬度转换成具体位置的接口,所以前端只需要获取当前用户的经纬度即可。OK,知道了要干的事情,接下来就开始干吧。
注:本文是基于 Vue + Typescript 技术栈的移动端项目撰写的
技术方向
经过一番的调研,有两种方式可以获取到经纬度:
- JS 原生 window.navigator.geolocation
- 第三方 API - 高德地图 API、百度地图 API、腾讯地图 API
到底选用哪种技术方案,需要结合开发难度、产品需求。经过一番对比,我最后选择的是第一种方案:使用 window.navigator.geolocation 。为什么呢,请听我娓娓道来!
JS API(第三方)
先来说说第三方 JS API。由于经纬度是实时通过卫星定位的,所以不管使用哪个 API 都需要以公司的名义注册开发账号,最关键的是申请 key,当然也可以以个人的名字注册。开发者需要将 JS API 脚本引入到项目中,在准备地图容器,初始化 AMap 实例,调用。
以高德 JS API 为例,注册个人账号
index.html
<html>
<head>
<!-- 引入 JS PAI -->
<script src="https://webapi.amap.com/maps?v=1.4.15&key=申请的 key 值" type="text/javascript"></script>
</head>
<body>
<div id="app"></div> // 单页面应用挂载 Dom 树
<div id="GaoDe" style="display: none;"></div> // 地图容器
</body>
</html>
新添加了一个 div,是绑定 Map 的地图容器。初始化地图之后,在绑定的地图容器中会生成一个地图,在不展示地图的情况下,与单页面挂载 Dom 分开使用就容易隐藏。
util.ts
export default class Utils {
getCurrentPosition():void{
let mapObj = new AMap.Map('GaoDe', {}) // html 中定义的 id 值
let geolocation;
mapObj.plugin(['AMap.Geolocation'], function () {
geolocation = new AMap.Geolocation({
enableHighAccuracy: true,//是否使用高精度定位,默认:true
timeout: 60000, //超过10秒后停止定位,默认:无穷大
maximumAge: 0, //定位结果缓存0毫秒,默认:0
convert: true, //自动偏移坐标,偏移后的坐标为高德坐标,默认:true
showButton: false, //显示定位按钮,默认:true
buttonPosition: 'LB', //定位按钮停靠位置,默认:'LB',左下角
buttonOffset: new AMap.Pixel(10, 20),//定位按钮与设置的停靠位置的偏移量,默认:Pixel(10, 20)
showMarker: false, //定位成功后在定位到的位置显示点标记,默认:true
showCircle: false, //定位成功后用圆圈表示定位精度范围,默认:true
panToLocation: false, //定位成功后将定位到的位置作为地图中心点,默认:true
zoomToAccuracy:false, //定位成功后调整地图视野范围使定位位置及精度范围视野内可见,默认:false
useNative:true
});
mapObj.addControl(geolocation);
geolocation.getCurrentPosition();
AMap.event.addListener(geolocation, 'complete', function(res){
console.log(res)
});//返回定位信息
AMap.event.addListener(geolocation, 'error', function(err){
console.log(err)
}); //返回定位出错信息
})
}
}
因为不需要在页面上展示地图,所以关于地图展示的内容全部设置为了 false 。
map.vue
// 引入
import utils from "@/util/utils";
// 调用
mounted():void{
utils.getCurrentPosition()
}
在展示效果之前,大家还需要注意:要在生产(预发)环境进行测试才能成功,本地环境会得到Geolocation permission denied.
的错误提示。
OK, 让我们来看一下效果。
当前网站访问位置信息,需要经过用户的授权。如果没有同意,是得不到经纬度的。
Good ! 经过一番折腾,终于得到了我们想要的。当然实现过程中,不会是一帆风顺的。
采坑一
因为地图需要一个容器,而且还是 id 值,小编想都没想,就把 index.html 中 app 放进去了,结果满屏的地图。
什么鬼?这不是我要的效果。
通过观察上面的内容我们就会发现,在初始化地图时,高德 JS API 在地图容器中注入了很多的代码,而且是直接放到了地图容器里,并没有在最外层再包一层。这也就解释了,为什么小编定义 <div id="GaoDe" style="display: none;"></div>
,与 app 容器分开了。
采坑二
相信大部分的开发者都跟小编一样,移动端的项目是在 PC 端开发,最后才真机测试。小编习惯使用 Chrome 来开发,但在测试时,即使是在 HTTPS 协议下,也提示 Get geolocation time out.Get ipLocation failed.
的错误。起初以为是自己设置或编码的错误,没往浏览器方面想,直到真机测试了一下,才发现自己的代码是没问题的。
网上对于这种情况的说法是,必须要 HTTPS 协议(小编的环境是呀),要升级站点 HTTPS,Google Chrome 浏览器本身定位就有问题,说法很多。小编最后也没有确认为什么会出现这样的效果。还是乖乖使用真机测试吧!
window.navigator.geolocation
说了这么多,终于进入重点了,也是小编最想分享给大家的。使用方式还是很简单的。
util.ts
export default class Utils {
getLocation():void{
if (navigator.geolocation){
let options = {
enableHighAccuracy : true,
maximumAge : 30000,
timeout :12000
}
navigator.geolocation.getCurrentPosition(getPosSuccess,getPosError,options);
}else{
console.log('当前环境不支持')
}
}
getPosSuccess(pos):void{
console.log(`纬度:${pos.coords.latitude} 经度:${pos.coords.longitude}`)
}
getPosError(err):void{
console.log(err)
}
}
H5.vue
// 引入
import utils from "@/util/utils";
// 调用
mounted():void{
utils.getLocation()
}
代码是不是很简单。开发者一定要相信,越简单的事情,坑会越大。运行项目,真机测试,结果信心大挫。
微信小程序 | 微信浏览器 | Safari/自带 | UC 浏览器 | |
---|---|---|---|---|
IOS | x | x | x | x |
Android | ✔️ | ✔️ | ✔️ | ✔️ |
IOS 基本上没有一个生效呀,为啥呢?翻了一遍 Geolocation.getCurrentPosition() 官网才注意到一句话:
This feature is available only in secure contexts (HTTPS), in some or all supporting browsers.(该特性仅在安全上下文(HTTPS)中可用,在一些或所有支持的浏览器中可用。)
那这样就好办了,使用预发环境在来一遍。
微信小程序 | 微信浏览器 | Safari/自带 | UC 浏览器 | |
---|---|---|---|---|
IOS | ✔️ | ✔️ | ✔️ | ✔️ |
Android | ✔️ | ✔️ | ✔️ | ✔️ |
赞,全部生效了,值得庆祝一下了!
为什么 IOS 非 HTTPS 协议获取不到?
window.navigator.geolocation.getCurrentPosition 是通过手机的 WebKit 定位。在 iOS 10中,苹果对 Webkit 定位权限进行了修改,所有定位请求的页面必须是 HTTPS 协议。如果是非HTTPS 网页,在 HTTP 协议下通过 H5 原生定位接口会返回错误。
使 IOS 支持 非 HTTPS 协议
有没有办法可以让 IOS 系统也支持本地服务呢?答案是肯定的,iframe 可以帮我们解决这个问题。
H5.vue
<template>
<div>
<div> 经度 {{getLongitude}},纬度 {{getLatitude}}</div>
<iframe id="mainIframe" src="javascript:(function(){
if(window.navigator.geolocation){
window.navigator.geolocation.getCurrentPosition(
function(position){
window.getLongitude = position.coords.longitude;
window.getLatitude = position.coords.latitude;
window.top.postMessage('success','*')
},
function(err){
console.log('不支持');
window.top.postMessage('error','*')
},
{
enableHighAccuracy : true,
maximumAge : 30000,
timeout :12000
}
)
}
}})()" style="display:none;"></iframe>
</div>
</template>
<script>
export default class Position extends Vue {
getLongitude:number = 0 // 经度
getLatitude:number = 0 // 纬度
mounted() {
// 监听 iframe postmessage传值
window.addEventListener('message', this.handleMessage);
}
handleMessage (e) {
if(e.data == 'success'){
let iframeWin = window.frames['mainIframe'].contentWindow
this.getLongitude = iframeWin.getLongitude
this.getLatitude = iframeWin.getLatitude
}
}
}
</script>
代码量很少,但是其中包含的知识点还是很丰富的,而且光看代码,大家可能会有很多的疑问。接下来,小编就做一番详述。
- iframe 与父级通信
在使用 iframe 时,大家一定要谨记一点:iframe 中的 window 与父级的 window 不是一个。iframe 中设置的 window 变量,在父级的 window 中是无法获取的。iframe 与父级的沟通可以通过 postMessage() 方法。
window.postMessage(message, targetOrigin)
使用 window.postMessage() 可以发送一个消息。
- message:将要发送到其他 window 的数据。注意:message 是 string 类型,如果想要传递一个对象,需要使用 JSON.stringify() 转换。
- targetOrigin:指定哪些窗口能接收到消息事件,其值可以是字符串
"*"
(表示无限制)或者一个 URI。
window.addEventListener('message', Function)
既然有发送消息,就肯定会有接收消息的方法。通过监听 window 中的 message 就可以实现。
window.frames[ID值].contentWindow
刚刚我们提到了 iframe 框架 window 与父级的 window 是不一样的。那我们怎样在父级中获取到 iframe window 的值呢,只能通过 postMessage 传值吗?NO,当然不是,为 iframe 设置唯一的 id 值,使用window.frames[ID值].contentWindow
就可以获取 iframe window。
-
不是异步,为什么要使用 postMessage 通知父级?
在 iframe 中直接设置到 window 变量中,父级使用 contentWindow 方法直接获取就好,为什么在设置完经纬度
window.getLongitude = position.coords.longitude;window.getLatitude = position.coords.latitude
之后,还要使用 postMessage 通讯一下?这与用户授权获取位置有关。不管是第三方,还是 H5 都是需要用户授权,所以 postMessage 通知是在用户点击确认授权时,触发的。如果页面初始的时候就需要获取经纬度,这点还是很重要。
-
为什么不将 url 中的 js 代码放到 JS 文件中?
这个问题还是跟第一个问题是一样的,如果放到 JS 文件中引入,那 JS 文件中的 window 指向的不就是父级了吗!
好了,啰嗦了这么久,你们是不是已经等不及看效果了。
选择
回到最初的问题,相信仔细阅读了这篇文章的童鞋,一定明白了,到最后我们为什么选择 window.navigator.geolocation 获取经纬度了。H5 获取经纬度简单,不需要繁琐的申请,引入第三方的 API 。 当然这只适用于当前的这种情况,因为我们对位置信息准确度不是很严格,对window.navigator.geolocation 的兼容性也是接受的。所以大家在做技术选择时,一定要考虑实际情况。
文章马上结束了,但是关于位置获取小编还有很多的疑问:
- window.navigator.geolocation 定位跟 WebKit 有关?
- 如果有关的话,window.navigator.geolocation 在浏览器中使用,应该跟 浏览器的 Webkit 有关,跟 IOS 系统是什么关系?
相信有很多小伙伴跟我一样,会有很多的疑问,希望大家可以相互交流!