# Geolocation API

> getCurrentPosition() 与 watchPosition() 在权限提示之后报告设备位置。本页列出 PositionOptions 成员、三种错误码各在何时出现，以及带回退分支的示例。

`navigator.geolocation` 通过一次性的 `getCurrentPosition()` 或持续的 `watchPosition()` 报告设备位置（纬度、经度、精度半径，以及可选的海拔、航向和速度），前提是浏览器先弹出它自己的权限提示。没有任何同步返回值：结果和失败都通过你传入的回调送达。

各引擎很早就都支持了：Chrome 5、Firefox 3.5、Safari 5（iOS 3）。安全上下文要求是后来加的：Chrome 50、Firefox 55 与 Safari 10 对 `localhost` 以外的 `http://` 源以 `PERMISSION_DENIED` 拒绝请求，但仍然暴露 `navigator.geolocation`（BCD `api.Geolocation.secure_context_required`）。`GeolocationCoordinates.toJSON()` 更新：Chrome 126、Firefox 129、Safari 18。

## 语法

```js
navigator.geolocation.getCurrentPosition(successCallback)
navigator.geolocation.getCurrentPosition(successCallback, errorCallback)
navigator.geolocation.getCurrentPosition(successCallback, errorCallback, options)

navigator.geolocation.watchPosition(successCallback)
navigator.geolocation.watchPosition(successCallback, errorCallback)
navigator.geolocation.watchPosition(successCallback, errorCallback, options)

navigator.geolocation.clearWatch(watchId)
```

`getCurrentPosition()` 返回 `undefined`。`watchPosition()` 返回一个大于零的 `long` 监视 id，位置每次变化都会调用 `successCallback`，直到调用 `clearWatch(watchId)`；文档不是 fully active 时返回 `0`。不要求用户激活，但第一次请求不在用户激活内时 Chrome 会记录一条 `[Violation]`（见实测）。

## 参数

两个请求方法共用三个参数。

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `successCallback` | `PositionCallback` | 是 | 接收 `GeolocationPosition`，含 `coords`（`GeolocationCoordinates`）与 `timestamp`（`EpochTimeStamp`）。 |
| `errorCallback` | `PositionErrorCallback?` | 否 | 接收带 `code` 与 `message` 的 `GeolocationPositionError`。不传时失败会被静默吞掉。 |
| `options` | `PositionOptions` | 否 | 下表的字典。 |

`PositionOptions` 有三个成员，都可选。

| 成员 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `enableHighAccuracy` | `boolean` | 否 | 默认 `false`。提示使用可得的最精确来源（手机上是 GPS），代价是首次定位更慢、更耗电。缓存位置只有在其 `[[isHighAccuracy]]` 标志与此值一致时才会复用。 |
| `timeout` | `[Clamp] unsigned long` | 否 | 默认 `0xFFFFFFFF` 毫秒。开始获取位置后最多等待的时间；等待权限提示和等待隐藏文档变为可见的时间不计入。`0` 可能立即失败。 |
| `maximumAge` | `[Clamp] unsigned long` | 否 | 默认 `0`。接受不早于这么多毫秒的缓存位置；`0` 强制重新定位。 |

## 异常

两个方法都不抛异常。失败以 `GeolocationPositionError` 送到 `errorCallback`，其 `code` 是该接口上三个常量之一。

| `code` | 常量 | 条件 |
|---|---|---|
| `1` | `PERMISSION_DENIED` | 用户或系统拒绝了权限；文档不允许使用 `geolocation` Permissions Policy 特性；或运行环境不是安全上下文。 |
| `2` | `POSITION_UNAVAILABLE` | 文档不是 fully active；或底层系统获取位置失败。 |
| `3` | `TIMEOUT` | 开始获取后 `options.timeout` 已超时。 |

`message` 是给开发者看的实现自定义文本，不适合展示给用户。Chrome 对权限失败报 `User denied Geolocation`，对 Permissions Policy 失败报 `Geolocation has been disabled in this document by permissions policy.`。用户给出的 `PERMISSION_DENIED` 会一直保持，直到用户自己更改设置；重复调用不会再次弹出提示。

:::observed
在 Chrome 中，页面加载时而不是点击时调用 `getCurrentPosition()`，DevTools Console 会打印 `[Violation] Only request geolocation information in response to a user gesture.`；从 `Permissions-Policy` 响应头排除了 `geolocation` 的文档发起请求，会打印 `Geolocation access has been blocked because of a permissions policy applied to the current document. See https://crbug.com/414348233 for more details.`，同时错误回调收到 `code` 为 `1`。两条字符串都定义在 Chromium 的 [`geolocation.cc`](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/core/geolocation/geolocation.cc) 中（chromium.googlesource.com）。
:::

## 示例

每个示例都检查 `"geolocation" in navigator`，并在 API 缺失或请求失败时给用户手动提供位置的途径。由按钮触发请求能让提示符合预期，也避免 Chrome 的违规警告。

### 一次性定位，失败时回退到手动输入地址

`timeout` 与非零的 `maximumAge` 让请求不会在无法定位的设备上一直挂着。所有错误码都落到同一个回退：显示地址输入框。

```js
const addressField = document.querySelector("#address");

function useManualEntry(reason) {
  addressField.hidden = false;
  addressField.placeholder = reason;
}

document.querySelector("#use-location").addEventListener("click", () => {
  if (!("geolocation" in navigator)) {
    useManualEntry("请输入所在城市");
    return;
  }
  navigator.geolocation.getCurrentPosition(
    ({ coords }) => {
      showNearby(coords.latitude, coords.longitude, coords.accuracy);
    },
    (error) => {
      const reasons = { 1: "位置权限被拒绝", 2: "无法获取位置", 3: "定位超时" };
      useManualEntry(reasons[error.code] ?? error.message);
    },
    { enableHighAccuracy: false, timeout: 10_000, maximumAge: 60_000 },
  );
});
```

`coords.accuracy` 是 95% 置信度的半径，单位米；「附近」类功能把几百米以内都视为足够，不必开 `enableHighAccuracy`。

### 跟踪路线，视图关闭时停止

`watchPosition()` 会一直占用定位硬件直到 `clearWatch()` 运行，所以把监视绑定到需要它的界面的生命周期上。

```js
let watchId = null;

function startTracking(onMove) {
  if (!("geolocation" in navigator)) return false;
  watchId = navigator.geolocation.watchPosition(
    (position) => onMove(position.coords),
    (error) => {
      stopTracking();
      console.warn(`跟踪已停止：${error.code} ${error.message}`);
    },
    { enableHighAccuracy: true, maximumAge: 0 },
  );
  return watchId !== 0;
}

function stopTracking() {
  if (watchId !== null) navigator.geolocation.clearWatch(watchId);
  watchId = null;
}

document.querySelector("#start").addEventListener("click", () => startTracking(updateMap));
document.querySelector("#stop").addEventListener("click", stopTracking);
window.addEventListener("pagehide", stopTracking);
```

返回值为 `0` 表示请求监视时文档不是 fully active，函数直接报告失败，而不是等待一个可能不会到来的错误回调。

### 弹出提示前先查询权限状态

`navigator.permissions.query({ name: "geolocation" })` 读取状态而不触发提示，页面可以只在能成功时显示「使用我的位置」按钮，或者解释如何重新开启被拒绝的权限。

```js
async function locationButtonState() {
  if (!("geolocation" in navigator)) return "unsupported";
  if (!navigator.permissions) return "unknown"; // 点击时再请求并处理错误
  const { state } = await navigator.permissions.query({ name: "geolocation" });
  return state; // "granted" | "prompt" | "denied"
}

const state = await locationButtonState();
document.querySelector("#use-location").hidden = state === "unsupported" || state === "denied";
document.querySelector("#denied-help").hidden = state !== "denied";
```

`"unknown"` 分支针对有 `geolocation` 却没有 `navigator.permissions` 的旧 WebView；页面在那里回退为点击时请求并读取错误码。

## 另请参阅

- [通知权限模型](/zh/reference/notifications/permissions/)，同一套 `granted` / `prompt` / `denied` 模型在另一项能力上的应用
- [Generic Sensor API 通用传感器](/zh/reference/capabilities/sensors/)，用于朝向与运动而非位置
- [本地网络访问](/zh/reference/capabilities/local-network-access/)，另一项受权限控制的能力
- [Geolocation API: getCurrentPosition() method](https://www.w3.org/TR/geolocation/#getcurrentposition-method)（w3.org）
- [Geolocation API: PositionOptions dictionary](https://www.w3.org/TR/geolocation/#position_options_interface)（w3.org）
- [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API)（developer.mozilla.org）