前置知识: HTML5

地理位置定位

4 min中级

Geolocation

前置要求与运行环境:示例使用 JavaScript 异步回调,需先完成 javascript/001-005 与 javascript/023(Promise/async)。

重要:Geolocation 只在安全上下文(Secure Context)可用——https:// 或 http://localhost;直接双击打开本地文件(file://)会被浏览器拒绝。 本地测试请用 Live Server 或 npx serve。

1. Geolocation API

if ('geolocation' in navigator) {
  navigator.geolocation.getCurrentPosition(
    (position) => {
      console.log('纬度:', position.coords.latitude);
      console.log('经度:', position.coords.longitude);
      console.log('精度:', position.coords.accuracy);
    },
    (error) => {
      switch (error.code) {
        case error.PERMISSION_DENIED:
          console.error('用户拒绝');
          break;
        case error.POSITION_UNAVAILABLE:
          console.error('位置不可用');
          break;
        case error.TIMEOUT:
          console.error('请求超时');
          break;
      }
    },
    { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 }
  );
}

watchPosition

const watchId = navigator.geolocation.watchPosition(
  (pos) => console.log(`位置: ${pos.coords.latitude}, ${pos.coords.longitude}`),
  (err) => console.error(err),
  { enableHighAccuracy: true }
);
navigator.geolocation.clearWatch(watchId);

2. Haversine 距离计算

d=2r⋅arcsin⁡(sin⁡2(φ2−φ12)+cos⁡(φ1)cos⁡(φ2)sin⁡2(λ2−λ12))d = 2r \cdot \arcsin\left(\sqrt{\sin^2\left(\frac{\varphi_2 - \varphi_1}{2}\right) + \cos(\varphi_1) \cos(\varphi_2) \sin^2\left(\frac{\lambda_2 - \lambda_1}{2}\right)}\right)
function haversineDistance(lat1, lon1, lat2, lon2) {
  const R = 6371;
  const toRad = (deg) => (deg * Math.PI) / 180;
  const dLat = toRad(lat2 - lat1);
  const dLon = toRad(lon2 - lon1);
  const a =
    Math.sin(dLat / 2) ** 2 +
    Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
  return R * 2 * Math.asin(Math.sqrt(a));
}

3. 地理围栏

class Geofence {
  constructor(centerLat, centerLng, radiusMeters) {
    this.center = { lat: centerLat, lng: centerLng };
    this.radius = radiusMeters;
  }
  contains(lat, lng) {
    return haversineDistance(this.center.lat, this.center.lng, lat, lng) * 1000 <= this.radius;
  }
}

Geolocation API 检测与获取

API 存在性检测 'geolocation' in navigator

// 检测浏览器是否支持 Geolocation API
if ('geolocation' in navigator) {
  // 支持,可调用相关 API
} else {
  // 不支持,需降级处理
}

获取当前位置 navigator.geolocation.getCurrentPosition(<success>, [error], [options])

// 异步获取设备当前位置(经纬度)
navigator.geolocation.getCurrentPosition(
  (position) => {
    console.log('纬度:', position.coords.latitude);   // 纬度(-180 ~ 180)
    console.log('经度:', position.coords.longitude);  // 经度(-90 ~ 90)
    console.log('精度:', position.coords.accuracy);   // 精度(米)
  },
  (error) => {
    console.error('错误码:', error.code, '消息:', error.message);
  },
  {
    enableHighAccuracy: true, // 是否启用高精度模式
    timeout: 10000,           // 超时时间(毫秒)
    maximumAge: 0             // 缓存位置最大有效期(毫秒),0 表示不使用缓存
  }
);

位置监听

持续监听位置变化 const watchId = navigator.geolocation.watchPosition(<success>, [error], [options])

// 持续监听位置变化(适用于导航、运动追踪等场景)
const watchId = navigator.geolocation.watchPosition(
  (pos) => {
    console.log(`当前位置: ${pos.coords.latitude}, ${pos.coords.longitude}`);
  },
  (err) => {
    console.error('监听失败:', err.message);
  },
  {
    enableHighAccuracy: true,
    timeout: 5000,
    maximumAge: 0
  }
);

停止位置监听 navigator.geolocation.clearWatch(<watchId>)

// 停止位置监听,释放资源
navigator.geolocation.clearWatch(watchId);

Position 对象属性

coords 属性表

属性类型说明
coords.latitudeDouble纬度(十进制度,范围 -90 ~ 90)
coords.longitudeDouble经度(十进制度,范围 -180 ~ 180)
coords.accuracyDouble位置精度(米)
coords.altitudeDouble海拔高度(米,null 表示不可用)
coords.altitudeAccuracyDouble海拔精度(米)
coords.headingDouble方向(度,正北顺时针,null 表示静止)
coords.speedDouble速度(米/秒,null 表示不可用)
timestampLong获取位置的时间戳(DOMTimeStamp)

错误处理

PositionError 错误码表

错误码常量名说明
1PERMISSION_DENIED用户拒绝了位置请求
2POSITION_UNAVAILABLE位置信息不可用
3TIMEOUT请求超时
0UNKNOWN_ERROR未知错误
// 错误处理示例
navigator.geolocation.getCurrentPosition(
  (pos) => console.log(pos.coords),
  (error) => {
    switch (error.code) {
      case error.PERMISSION_DENIED:
        console.error('用户拒绝授权');
        break;
      case error.POSITION_UNAVAILABLE:
        console.error('位置不可用');
        break;
      case error.TIMEOUT:
        console.error('请求超时');
        break;
      default:
        console.error('未知错误:', error.message);
    }
  }
);

Permissions API 权限查询

查询地理定位权限状态 navigator.permissions.query({ name: 'geolocation' })

// 查询当前地理位置权限状态
navigator.permissions.query({ name: 'geolocation' }).then((result) => {
  console.log('权限状态:', result.state); // granted | denied | prompt
  result.onchange = () => {
    console.log('权限变更:', result.state);
  };
});

Haversine 距离计算

计算两点间球面距离 haversineDistance(<lat1>, <lon1>, <lat2>, <lon2>)

// 使用 Haversine 公式计算地球表面两点间最短距离(千米)
function haversineDistance(lat1, lon1, lat2, lon2) {
  const R = 6371; // 地球半径(千米)
  const toRad = (deg) => (deg * Math.PI) / 180;
  const dLat = toRad(lat2 - lat1);
  const dLon = toRad(lon2 - lon1);
  const a =
    Math.sin(dLat / 2) ** 2 +
    Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
  return R * 2 * Math.asin(Math.sqrt(a));
}

// 示例:北京到上海的距离
const distance = haversineDistance(39.9042, 116.4074, 31.2304, 121.4737);
console.log(`距离: ${distance.toFixed(2)} 千米`);

地理围栏

Geofence 类实现 new Geofence(<centerLat>, <centerLng>, <radiusMeters>)

// 地理围栏:判断设备是否进入指定圆形区域
class Geofence {
  constructor(centerLat, centerLng, radiusMeters) {
    this.center = { lat: centerLat, lng: centerLng };
    this.radius = radiusMeters; // 半径(米)
  }

  // 判断指定坐标是否在围栏内
  contains(lat, lng) {
    const distanceKm = haversineDistance(this.center.lat, this.center.lng, lat, lng);
    return distanceKm * 1000 <= this.radius;
  }
}

// 使用示例
const fence = new Geofence(39.9042, 116.4074, 500); // 北京中心 500 米范围
console.log(fence.contains(39.9050, 116.4080)); // true/false

注意事项

  • HTTPS 要求:Geolocation API 仅在安全上下文(HTTPS 或 localhost)中可用
  • 用户授权:首次调用会弹出权限请求,用户拒绝后返回 PERMISSION_DENIED
  • 精度限制:enableHighAccuracy: true 会消耗更多电量(使用 GPS)
  • 移动设备:结合 watchPosition 可实现导航功能,但需注意电池消耗
  • 隐私保护:不得在未经用户同意的情况下收集或上传位置数据

动手试试

入门版(必做)

  1. 在页面显示“获取我的位置”按钮,点击后调用 getCurrentPosition 展示经纬度与精度;
  2. 拒绝授权一次,观察错误分支的提示;
  3. 用高德或腾讯地图的 JS API,把坐标显示到地图上。

进阶版(选做)

  1. 用 watchPosition 实现“移动距离统计”,累积相邻两点间的 Haversine 距离;
  2. 用 Permissions API 在调用前查询权限状态并提示用户;
  3. 实现一个简单的“进入店铺范围提醒”地理围栏。

核心知识点

一句话记住定位:getCurrentPosition 取一次,watchPosition 持续跟;clearWatch 要记得,权限被拒要兜底。

  • getCurrentPosition(success, error, options) 单次获取位置;
  • watchPosition 持续监听,返回 watchId,用 clearWatch 停止;
  • position.coords 提供经纬度、精度、海拔、速度等;
  • 错误码:拒绝授权、位置不可用、超时;
  • Haversine 公式计算球面距离,实际项目可用 turf.js;
  • 定位涉及隐私:只在必要时申请,用完即停。

注意事项与改进建议

问题点说明改进方案
页面加载就请求定位用户无感知,拒绝率高由用户点击触发并说明用途
持续 watchPosition 不停止耗电、隐私风险用完 clearWatch,切后台暂停
把经纬度当平面坐标算距离结果严重偏差使用 Haversine 或地图库
忽略错误分支拒绝后页面无反馈提供降级文案与手动输入
明文传输位置隐私泄露风险使用 HTTPS
未经许可保存位置侵犯用户隐私征得同意并允许删除

扩展学习

  • 地图服务:高德/腾讯/Google Maps JS API 的坐标展示与逆地理编码;
  • 权限体系:html5/240-HTML5OfflineStorageWebAPI 中 Notification 等权限 API;
  • 实时位置:html5/320-WebSocket 传输位置实现共享定位;
  • 隐私合规:了解 GDPR/个人信息保护法对位置数据的处理要求。