# 安装横幅与 beforeinstallprompt

> 围绕 beforeinstallprompt 构建安装横幅，保留浏览器安装入口，并为没有该事件的浏览器准备可用路径。

`beforeinstallprompt` 事件给 Chromium 页面一个只能使用一次的对象，应用自己的横幅或按钮可以借它打开浏览器安装提示。页面可以延后提示，但在收到事件、确认这条安装路径可用之前，应让自己的安装控件保持隐藏。

BCD `api.BeforeInstallPromptEvent` 把 Chrome 标为从 44 支持，把 Samsung Internet Android 标为从 5.0 支持；Edge 记录为 `"mirror"`。[兼容性数据](/zh/compatibility/install-prompt/)列出目标浏览器。BCD 未记录 Firefox 或 Safari 支持；其 `api.BeforeInstallPromptEvent.prompt` 条目把 Chrome 44 至 75 记录为部分实现，Promise 兑现时没有返回值，Chrome 76 起才记录结果对象。这个事件尚未标准化，在 Firefox 和 Safari 中不存在，因此安装横幅必须有明确的无事件路径。

## 工作原理

在 `window` 上监听事件，调用 `preventDefault()`，保存事件对象，然后显示安装控件。在安装控件的点击处理器中调用 `prompt()`。MDN 指出，`prompt()` 必须在用户动作的处理器中调用，并且每个 `BeforeInstallPromptEvent` 只能调用一次。提示流程结束后应清除已保存的事件。代码需要覆盖 Chrome 44 至 75 时，应从 `userChoice` 读取结果，因为这些版本的 `prompt()` 不返回结果对象。

| 状态 | 横幅动作 | 页面获得的依据 |
|---|---|---|
| 事件尚未触发 | 隐藏程序化安装按钮 | 页面没有可用的提示对象 |
| `beforeinstallprompt` 触发 | 保存事件并显示按钮 | 浏览器判定应用可使用这条安装流程 |
| `prompt()` 结束 | 隐藏按钮并丢弃事件 | `userChoice` 提供 `"accepted"` 或 `"dismissed"`；Chrome 76 起 `prompt()` 本身才返回该结果对象 |
| `appinstalled` 触发 | 隐藏安装推广并清除状态 | 浏览器报告应用已安装 |
| 没有事件 API | 不调用 `prompt()`，改为显示浏览器对应的帮助 | BCD 未记录 Firefox 或 Safari 支持 `BeforeInstallPromptEvent` |

`preventDefault()` 控制支持浏览器中该事件的默认安装推广。Chrome 的 mini-infobar 更新说明，从 Chrome 76 起，这个调用可阻止 mini-infobar。页面横幅本身不会安装应用；它只是向用户提供控件，稍后由控件调用浏览器拥有的 `prompt()` 方法。

MDN 将 `outcome` 为 `"accepted"` 定义为用户安装了应用。还应监听 `appinstalled`，这样从保存事件之外的浏览器 UI 完成安装时也能撤下推广。MDN 安装指南也要求调用 `prompt()` 后清除已保存的事件，因为同一个事件不能再次使用。

应把事件对象和横幅当成同一份状态。导航会丢弃 JavaScript 对象，新的文档要等待自己的事件；用户拒绝提示后，这个对象也不再有用。MDN 的事件示例在 `prompt()` 返回后隐藏按钮并清除引用，`appinstalled` 示例则在用户从其他浏览器入口完成安装时做同样的清理。这样，页面不会留下仍然可见、却指向已经消耗或不存在事件的控件。

最终确认仍由浏览器负责。页面可以决定何时显示自己的推广，以及何时调用 `prompt()`；在已记录的 Chrome 支持范围内，`userChoice` 会报告用户在浏览器 UI 中作出的选择。应用分析应读取该结果，不应使用页面自己的成功状态替代浏览器确认。当结果为 `"dismissed"` 时，撤下与本次事件对应的横幅，等待以后新的 `beforeinstallprompt` 事件重新建立状态。

BCD 对 Firefox 和 Safari 记录的是 `version_added: false`。特性检测可以选择说明文字，却不能生成浏览器安装提示。回退说明应保持简短，同时保留浏览器菜单或自带安装入口。

| 目标 | Chromium 事件路径 | 无事件路径 |
|---|---|---|
| 显示页内安装操作 | 收到 `beforeinstallprompt` 后显示 | 保持隐藏 |
| 打开浏览器确认界面 | 在点击处理中调用已保存事件的 `prompt()` | 说明浏览器从哪里提供安装入口 |
| 移除失效推广 | 处理 `appinstalled`，使用后清除事件 | 页面加载时重新检查已安装显示模式 |
| 处理用户拒绝 | 隐藏控件，等待新的事件 | 没有可读取的提示结果 |

:::caution
MDN 的 [`prompt()` 参考](https://developer.mozilla.org/en-US/docs/Web/API/BeforeInstallPromptEvent/prompt)明确要求在用户动作处理器中调用该方法，并规定一个事件只能调用一次。重复使用保存的对象，或从计时器调用，都不符合这份契约。调用结束后应立即清除对象。

Chromium 的 [`app-banner-event-prompt.html`](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/web_tests/app_banner/app-banner-event-prompt.html)预期缺少用户手势的调用以 `The prompt() method must be called with a user gesture` 拒绝。该文本可用于诊断 Chromium，不是跨浏览器契约。
:::

## 示例

横幅在 HTML 中默认隐藏。只有浏览器向页面交付有效事件对象后，JavaScript 才显示它。

### 检测运行时支持

等待 `beforeinstallprompt` 前，先检测窗口是否有对应的事件处理器属性。支持该事件时，安装按钮保持隐藏，直到事件触发；不支持时，代码改为显示浏览器安装说明。BCD 未记录 Firefox 或 Safari 支持 `BeforeInstallPromptEvent`。

```html
<button data-event-install type="button" hidden>安装</button>
<p data-event-fallback hidden>如果浏览器提供安装命令，请从浏览器界面使用该命令。</p>

<script>
  var installButton = document.querySelector('[data-event-install]');
  var installFallback = document.querySelector('[data-event-fallback]');

  if ('onbeforeinstallprompt' in window) {
    window.addEventListener('beforeinstallprompt', function (event) {
      event.preventDefault();
      installButton.hidden = false;
    });
  } else {
    installButton.hidden = true;
    installFallback.hidden = false;
  }
</script>
```

下一个示例会为支持分支补上点击处理器和提示流程。

### 把安装提示延后到横幅点击

下面是完整的 HTML 示例。它把浏览器事件保留到用户点击，从支持范围更广的 `userChoice` Promise 读取结果，不会解引用 `prompt()` 的返回值；`appinstalled` 触发时也会撤下横幅。

```html
<aside data-install-banner hidden>
  <p>安装此应用，以便更快打开。</p>
  <button type="button">安装</button>
</aside>
<p><output data-install-status aria-live="polite"></output></p>

<script>
  (function () {
    var installEvent = null;
    var banner = document.querySelector('[data-install-banner]');
    var status = document.querySelector('[data-install-status]');
    var installButton = banner && banner.querySelector('button');
    if (!banner || !status || !installButton) return;

    window.addEventListener('beforeinstallprompt', function (event) {
      event.preventDefault();
      installEvent = event;
      installButton.disabled = false;
      banner.hidden = false;
    });

    installButton.addEventListener('click', function () {
      if (!installEvent) return;
      var pendingEvent = installEvent;
      installEvent = null;
      installButton.disabled = true;

      pendingEvent.prompt().then(function () {
        return pendingEvent.userChoice;
      }).then(function (choice) {
        status.textContent = '安装提示结果：' + choice.outcome;
        banner.hidden = true;
      }, function (error) {
        status.textContent = '无法打开安装提示：' + error.name;
        banner.hidden = true;
      });
    });

    window.addEventListener('appinstalled', function () {
      installEvent = null;
      banner.hidden = true;
      status.textContent = '应用已安装。';
    });
  }());
</script>
```

按钮保持隐藏，直到事件真正触发；仅检测到属性不会改变这个状态。这使界面与浏览器对本次访问的判定保持一致。

### 处理没有事件的浏览器

失败路径会选择帮助说明，不会等待 BCD 在 Firefox 或 Safari 中未记录的事件。应用以 standalone 模式打开后，检查结果会隐藏说明。

```html
<p data-install-help hidden>
  如果浏览器提供安装命令，请从浏览器界面使用该命令。
</p>
<p data-installed-state hidden>此应用已经作为已安装应用打开。</p>

<script>
  var help = document.querySelector('[data-install-help]');
  var installedState = document.querySelector('[data-installed-state]');
  var installed = window.matchMedia('(display-mode: standalone)').matches;

  if (help && installedState) {
    installedState.hidden = !installed;
    help.hidden = installed || 'onbeforeinstallprompt' in window;
  }
</script>
```

回退分支没有 `BeforeInstallPromptEvent`，所以不调用 `prompt()`。应根据已测试的浏览器界面调整提示文字。[安装提示兼容性数据](/zh/compatibility/install-prompt/)给出引擎层面的支持差异，[iOS 安装 API 实验记录](/zh/lab/behaviour/install-apis-on-ios/)则单独保存实测行为。

## 更正（2026-10-31）

此次更正使 `"accepted"` 结果的说明与引用来源保持一致。MDN 的 [`prompt()` 参考](https://developer.mozilla.org/en-US/docs/Web/API/BeforeInstallPromptEvent/prompt)将 `"accepted"` 定义为用户安装了应用，[web.dev 的应用内安装指南](https://web.dev/articles/customize-install)说明 `userChoice` 可报告用户是否通过应用内界面完成安装。原 `publishedAt` 日期保持不变。

## 另请参阅

- [Manifest Incubations：安装提示](https://wicg.github.io/manifest-incubations/)（wicg.github.io）
- [提供应用内安装体验](https://web.dev/articles/customize-install)（web.dev）
- [beforeinstallprompt 事件](/zh/reference/installation/install-prompt/)
- [安装提示 UX](/zh/reference/installation/install-prompt-ux/)
- [iOS 添加到主屏幕](/zh/reference/installation/ios-add-to-home-screen/)