该如何实现经纬度获取(vue 实战)

6,202 阅读8分钟

一个前端程序猿,在最近的开发过程中遇到了这样的需求:需要获取用户的当前位置,但是由于后端有现成的将经纬度转换成具体位置的接口,所以前端只需要获取当前用户的经纬度即可。OK,知道了要干的事情,接下来就开始干吧。

注:本文是基于 Vue + Typescript 技术栈的移动端项目撰写的

技术方向

经过一番的调研,有两种方式可以获取到经纬度:

  1. JS 原生 window.navigator.geolocation
  2. 第三方 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 浏览器
IOSxxxx
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>

代码量很少,但是其中包含的知识点还是很丰富的,而且光看代码,大家可能会有很多的疑问。接下来,小编就做一番详述。

  1. 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。

  1. 不是异步,为什么要使用 postMessage 通知父级?

    在 iframe 中直接设置到 window 变量中,父级使用 contentWindow 方法直接获取就好,为什么在设置完经纬度window.getLongitude = position.coords.longitude;window.getLatitude = position.coords.latitude 之后,还要使用 postMessage 通讯一下?

    这与用户授权获取位置有关。不管是第三方,还是 H5 都是需要用户授权,所以 postMessage 通知是在用户点击确认授权时,触发的。如果页面初始的时候就需要获取经纬度,这点还是很重要。

  2. 为什么不将 url 中的 js 代码放到 JS 文件中?

    这个问题还是跟第一个问题是一样的,如果放到 JS 文件中引入,那 JS 文件中的 window 指向的不就是父级了吗!

好了,啰嗦了这么久,你们是不是已经等不及看效果了。

选择

回到最初的问题,相信仔细阅读了这篇文章的童鞋,一定明白了,到最后我们为什么选择 window.navigator.geolocation 获取经纬度了。H5 获取经纬度简单,不需要繁琐的申请,引入第三方的 API 。 当然这只适用于当前的这种情况,因为我们对位置信息准确度不是很严格,对window.navigator.geolocation 的兼容性也是接受的。所以大家在做技术选择时,一定要考虑实际情况。

文章马上结束了,但是关于位置获取小编还有很多的疑问:

  • window.navigator.geolocation 定位跟 WebKit 有关?
  • 如果有关的话,window.navigator.geolocation 在浏览器中使用,应该跟 浏览器的 Webkit 有关,跟 IOS 系统是什么关系?

相信有很多小伙伴跟我一样,会有很多的疑问,希望大家可以相互交流!