# 屏幕方向 API（锁定与解锁）

> 读取屏幕方向变化，请求和解除方向锁定，处理失败请求，并在不支持锁定时提供响应式布局回退。

屏幕方向 API 通过 `screen.orientation` 暴露文档的方向。它报告当前类型和角度、触发 `change` 事件，并提供请求方向锁定的 `lock()` 与解除锁定的 `unlock()`（[Screen Orientation](https://w3c.github.io/screen-orientation/)，W3C）。

## 支持范围

MDN 将 `ScreenOrientation` 接口标为广泛可用，但将 `lock()` 标为有限可用。MDN 还指出，方向锁定通常在移动设备的浏览器上下文进入全屏时启用；规范则允许用户代理把全屏或已安装的 Web 应用设为锁定前提（[ScreenOrientation: lock() method](https://developer.mozilla.org/en-US/docs/Web/API/ScreenOrientation/lock)，MDN；[Screen Orientation](https://w3c.github.io/screen-orientation/#dom-screenorientation-lock)，W3C）。

## 语法

```js
screen.orientation.type
screen.orientation.angle
screen.orientation.addEventListener("change", listener)

await screen.orientation.lock(orientation)
screen.orientation.unlock()
```

应用锁定后，`lock()` 返回的 Promise 以 `undefined` 兑现。`unlock()` 返回 `undefined`；规范解释，用户代理可能无法预知回到默认方向是否会改变屏幕（[Screen Orientation](https://w3c.github.io/screen-orientation/#dom-screenorientation-unlock)，W3C）。

## 成员

`screen.orientation` 是 `ScreenOrientation` 对象，包含规范定义的以下成员。

| 成员 | 类型 | 说明 |
|---|---|---|
| `type` | `OrientationType`（只读） | 当前值：`portrait-primary`、`portrait-secondary`、`landscape-primary` 或 `landscape-secondary`。 |
| `angle` | `unsigned short`（只读） | 屏幕相对其自然方向逆时针旋转的角度。 |
| `lock(orientation)` | `Promise<undefined>` | 请求锁定到一个 `OrientationLockType` 值。 |
| `unlock()` | `undefined` | 解除活动锁定并回到默认方向。 |
| `onchange` | `EventHandler` | 处理报告的方向类型或角度改变时触发的 `change` 事件。 |

`orientation` 参数接受 `any`、`natural`、`landscape`、`portrait`、`portrait-primary`、`portrait-secondary`、`landscape-primary` 或 `landscape-secondary`。规范允许用户代理或平台只支持其中一部分锁定值（[OrientationLockType enum](https://w3c.github.io/screen-orientation/#enumdef-orientationlocktype)，W3C）。

## 异常

规范为 `lock()` 定义了以下失败情况。

它的通用安全检查也会同步应用于 `unlock()`。

| 异常 | 条件 |
|---|---|
| `InvalidStateError` | 文档不是受到用户关注的顶级可遍历对象的 fully active 后代。 |
| `SecurityError` | 文档已隐藏，或设置了 sandboxed orientation lock 标志。 |
| `NotSupportedError` | 用户代理无法锁定到请求的值，或平台、用户偏好或其他限制阻止了尝试。 |
| `NotAllowedError` | 用户代理要求锁定前提，例如全屏或已安装的 Web 应用，而文档不满足条件。 |
| `AbortError` | 另一个 `lock()` 请求会取消待处理的锁定请求。屏幕存在活动方向锁定时，`unlock()` 也会取消该请求。 |

`unlock()` 会先检查屏幕的活动方向锁定。该值为 `null` 时，方法会在检查待处理的锁定 Promise 前返回 `undefined`。否则，待处理的锁定 Promise 会以 `AbortError` 拒绝，随后用户代理应用默认方向锁定（[Screen Orientation](https://w3c.github.io/screen-orientation/#dom-screenorientation-unlock)，W3C）。

:::caution
如果用户代理要求锁定前提，而文档没有满足条件，规范要求 `lock()` 以 `NotAllowedError` 拒绝。常见前提包括全屏或已安装的 Web 应用（[Screen Orientation](https://w3c.github.io/screen-orientation/#dom-screenorientation-lock)，W3C）。
:::

## 示例

下面的示例会先检测要使用的屏幕方向成员。各自的回退分支保留响应式行为，不尝试不可用的操作。

### 检测支持后锁定查看器

把这段脚本粘贴到页面中。它会创建按钮和状态输出，锁定前请求全屏，并在下一次点击时解除两种状态。

```js
const button = document.createElement("button");
const status = document.createElement("output");
button.textContent = "锁定横向";
document.body.append(button, status);

let locked = false;

button.addEventListener("click", async () => {
  const orientation = screen.orientation;

  if (typeof orientation?.lock !== 'function') {
    const axis = matchMedia("(orientation: portrait)").matches
      ? "纵向"
      : "横向";
    status.value = `锁定不可用；响应式布局当前为${axis}。`;
    return;
  }

  try {
    if (!locked) {
      if (!document.fullscreenElement) {
        await document.documentElement.requestFullscreen();
      }
      await orientation.lock("landscape");
      locked = true;
      button.textContent = "解除方向锁定";
      status.value = `已锁定：${orientation.type}，${orientation.angle} 度。`;
    } else {
      orientation.unlock();
      if (document.fullscreenElement) await document.exitFullscreen();
      locked = false;
      button.textContent = "锁定横向";
      status.value = "方向已解锁。";
    }
  } catch (error) {
    status.value = `${error.name}：未锁定方向。`;
  }
});
```

catch 分支处理被拒绝的全屏和方向请求。功能检测分支把布局控制权留给 CSS，因此 `lock()` 缺失时页面仍可使用。

### 跟随方向变化

在 `change` 处理函数中重新读取 `type` 与 `angle`，因为该事件表示这些报告值已经改变。

```js
function renderOrientation() {
  const current = screen.orientation;
  document.documentElement.dataset.orientation = current.type;
  console.log(`${current.type}：${current.angle} 度`);
}

if (screen.orientation) {
  screen.orientation.addEventListener("change", renderOrientation);
  renderOrientation();
} else {
  const portrait = matchMedia("(orientation: portrait)");
  const renderFallback = () => {
    document.documentElement.dataset.orientation = portrait.matches
      ? "portrait"
      : "landscape";
  };
  portrait.addEventListener("change", renderFallback);
  renderFallback();
}
```

回退保留纵向或横向的区别，但不会虚构媒体查询没有提供的 primary 或 secondary 方向值。

## 另请参阅

- [orientation 清单成员](/zh/reference/manifest/orientation/)
- [display 清单成员](/zh/reference/manifest/display/)
- [Window Management API](/zh/reference/capabilities/window-management/)
- [Screen Orientation](https://w3c.github.io/screen-orientation/)（w3.org）
- [ScreenOrientation interface](https://developer.mozilla.org/en-US/docs/Web/API/ScreenOrientation)（developer.mozilla.org）
- [ScreenOrientation: lock() method](https://developer.mozilla.org/en-US/docs/Web/API/ScreenOrientation/lock)（developer.mozilla.org）