# VirtualKeyboard API

> navigator.virtualKeyboard.overlaysContent 阻止屏幕键盘压缩视口；boundingRect、geometrychange、show()/hide()、keyboard-inset env() 变量与失效前提。

`navigator.virtualKeyboard` 让页面接管屏幕键盘弹出时浏览器通常自动完成的布局工作。把 `overlaysContent` 设为 `true` 后浏览器不再压缩视口，`geometrychange` 事件与 `boundingRect` 报告键盘所在位置，六个 `keyboard-inset-*` CSS 环境变量把同一个矩形暴露给样式表，`show()` 与 `hide()` 则为标记了 `virtualkeyboardpolicy="manual"` 的可编辑元素请求键盘。

该接口在 Chrome 94 与 Edge 94 中发布，Opera、Samsung Internet、Android WebView 与 Meta Quest Browser 镜像同一实现。Firefox（[Bugzilla 1730568](https://bugzil.la/1730568)）与 Safari（[WebKit bug 230225](https://webkit.org/b/230225)）均未实现（BCD `api.VirtualKeyboard`）。在没有系统屏幕键盘的设备上，对象存在但 `boundingRect` 始终为零，`geometrychange` 不会触发。

## 语法

```js
navigator.virtualKeyboard.overlaysContent = true
navigator.virtualKeyboard.boundingRect
navigator.virtualKeyboard.show()
navigator.virtualKeyboard.hide()
navigator.virtualKeyboard.addEventListener('geometrychange', handler)
```

```css
.composer {
  padding-bottom: env(keyboard-inset-height, 0px);
}
```

`show()` 与 `hide()` 返回 `undefined`，并以异步方式生效：浏览器请求系统显示或隐藏键盘，等待完成，更新 `boundingRect`，然后触发 `geometrychange`。`boundingRect` 是相对布局视口、以 CSS 像素计的 `DOMRect`；`keyboard-inset-top`、`-right`、`-bottom`、`-left`、`-width`、`-height` 六个环境变量由同一个矩形更新，键盘从未出现过时读作 `0px`。`VirtualKeyboard` 只暴露在 `Window` 上。

## 成员

该接口有两个属性、两个方法和一个事件。`show()` 与 `hide()` 不接受参数；它们的前提条件列在各自条目里，因为不满足前提的调用会被直接丢弃而没有异常。

| 成员 | 类型 | 说明 |
|---|---|---|
| `overlaysContent` | `boolean`，默认 `false` | 为 `true` 时，浏览器不得为键盘调整文档视口或视觉视口的尺寸；键盘覆盖在页面之上，页面根据 `boundingRect` 自行调整位置。只在顶层浏览上下文中生效。 |
| `boundingRect` | `DOMRect`（只读） | 当前键盘矩形；键盘隐藏时全部为零。 |
| `show()` | `undefined` | 请求显示键盘。除非窗口拥有 sticky activation、焦点元素是表单控件或编辑宿主、该元素带有 `virtualkeyboardpolicy="manual"`、且其 `inputmode` 不是 `none`，否则被忽略。 |
| `hide()` | `undefined` | 在同样的激活与 `virtualkeyboardpolicy="manual"` 条件下收起键盘。 |
| `geometrychange` 事件 | `Event`（`event.target.boundingRect`） | 键盘显示、隐藏或尺寸变化且 `boundingRect` 更新后触发。 |

可编辑元素上的 `virtualkeyboardpolicy` 内容属性取值 `auto`（默认：聚焦元素即显示键盘）或 `manual`（由页面自己调用 `show()`）；`inputmode="none"` 无论策略如何都会抑制键盘。

## 异常

无。

setter 与两个方法都不抛出。前提条件不满足的 `show()` 或 `hide()` 直接返回；在 iframe 内给 `overlaysContent` 赋值会被 setter 接受但不产生效果。Chromium 把这两种情况报告为控制台警告，而不是异常。

:::observed
在 iframe 内执行 `navigator.virtualKeyboard.overlaysContent = true` 时，Chrome 在 DevTools Console 以 Warning 级别打印 `Setting overlaysContent is only supported from the top level browsing context`；在任何用户交互之前（例如从 `DOMContentLoaded`）调用 `show()` 时打印 `Calling show is only supported if user has interacted with the page`。两条字符串由 Chromium 的 [`virtual_keyboard.cc`](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/virtualkeyboard/virtual_keyboard.cc) 发出（chromium.googlesource.com）。
:::

## 示例

两个示例都检测 `'virtualKeyboard' in navigator`。缺失时保留浏览器的默认行为（压缩视口），并借助 Firefox 91 与 Safari 13 都实现的 `window.visualViewport` 得知键盘占去了多少空间。

### 把消息输入框固定在键盘上方

聊天输入框应当紧贴键盘上沿，而不是随页面滚走。有 API 时页面退出视口压缩，并按键盘高度给输入框加内边距；没有 API 时输入框跟随视觉视口的底边。

```js
const composer = document.querySelector('.composer');

if ('virtualKeyboard' in navigator) {
  navigator.virtualKeyboard.overlaysContent = true;
  navigator.virtualKeyboard.addEventListener('geometrychange', (event) => {
    const { height } = event.target.boundingRect;
    composer.style.setProperty('--keyboard-height', `${height}px`);
  });
} else if (window.visualViewport) {
  const vv = window.visualViewport;
  vv.addEventListener('resize', () => {
    const covered = window.innerHeight - vv.height - vv.offsetTop;
    composer.style.setProperty('--keyboard-height', `${Math.max(0, covered)}px`);
  });
}
```

```css
.composer {
  position: fixed;
  bottom: 0;
  padding-bottom: var(--keyboard-height, env(keyboard-inset-height, 0px));
}
```

这条 CSS 回退链意味着：支持环境变量但尚未触发 `geometrychange` 的浏览器仍得到 `0px`，既没有 API 也没有该变量的浏览器则得到普通的固定输入框。

### 为基于 canvas 的编辑器唤起键盘

画在 `<canvas>` 上的电子表格没有可聚焦的 `<input>`，于是它放一个隐藏的、带 `virtualkeyboardpolicy="manual"` 的 `contenteditable` 元素，并在单元格被点按时调用 `show()`。没有该 API 的浏览器回退为聚焦该元素，由默认的 `auto` 策略显示键盘。

```html
<div id="cell-input" contenteditable="true" virtualkeyboardpolicy="manual" inputmode="text"></div>
```

```js
const input = document.querySelector('#cell-input');
const canvas = document.querySelector('canvas');

canvas.addEventListener('pointerup', () => {
  input.focus();
  if ('virtualKeyboard' in navigator) {
    navigator.virtualKeyboard.show(); // 这次点按提供了 sticky activation
  }
});

document.querySelector('#done').addEventListener('click', () => {
  if ('virtualKeyboard' in navigator) {
    navigator.virtualKeyboard.hide();
  } else {
    input.blur(); // 默认策略：失去焦点即收起键盘
  }
});
```

由于 `show()` 依赖该策略属性，从标记里删掉 `virtualkeyboardpolicy="manual"` 会让这次调用悄悄变成空操作而没有报错；属性与调用要放在一起维护。

## 另请参阅

- [Media Session API](/zh/reference/capabilities/media-session/)，另一项 Chromium 先行的系统 UI 集成
- [Window Management API](/zh/reference/capabilities/window-management/)，关于屏幕而非键盘的几何问题
- [PWA 的 Core Web Vitals：LCP、INP 与 CLS](/zh/reference/performance/core-web-vitals/)，键盘引起的视口压缩在那里表现为布局偏移
- [VirtualKeyboard API: the VirtualKeyboard interface](https://w3c.github.io/virtual-keyboard/#the-virtualkeyboard-interface)（w3c.github.io）
- [Full control with the VirtualKeyboard API](https://developer.chrome.com/docs/web-platform/virtual-keyboard)（developer.chrome.com）
- [Bugzilla 1730568: Implement the VirtualKeyboard API](https://bugzil.la/1730568)（bugzilla.mozilla.org）
- [WebKit bug 230225: Implement the VirtualKeyboard API](https://webkit.org/b/230225)（webkit.org）