# 安装提示 UX

> 页面只有三个可安装性信号（beforeinstallprompt、display-mode、appinstalled），各浏览器自带的安装入口长什么样，过早提示的代价，以及按平台显示入口的代码。

安装提示 UX 是一组决策：页面何时显示自己的安装入口、文案说什么、在不存在程序化提示的地方做什么。只有 Chromium 浏览器（Android 上的 Chrome 68、桌面端 Chrome 73、Edge 79、Samsung Internet 9.0）会给页面一个可以推迟的 `beforeinstallprompt` 事件；iOS 上的 Safari、macOS Safari 17、Firefox for Android 与 Windows 上的 Firefox 143 只能从各自的菜单安装，页面在那里的任务是描述路径，而不是打开它（BCD `api.BeforeInstallPromptEvent`）。

## 工作原理

页面关于可安装性恰好只有三个信号，所有安装 UX 都由它们构成。

1. **`window` 上的 `beforeinstallprompt`**（仅 Chromium）。它的到来证明浏览器认为页面可安装，并会在之后的用户手势中响应 `prompt()`。在它触发之前，安装按钮是页面无法兑现的承诺；调用 `preventDefault()` 之后，浏览器自己的横幅会一直隐藏到 `prompt()` 被调用。
2. **`display-mode`**。`matchMedia('(display-mode: standalone)')`（以及 `window-controls-overlay`、`minimal-ui`、`fullscreen`）告诉页面它已经以安装态运行，这是唯一必须让所有安装入口消失的状态。iOS Safari 还额外暴露旧的布尔属性 `navigator.standalone`。
3. **`window` 上的 `appinstalled`**（Chromium）。从任何入口完成安装后触发，因此即便用户用的是地址栏图标而不是页面按钮，页面也能隐藏按钮并记录转化。

不写任何页面代码时浏览器显示什么，各平台并不相同，页面文案必须与之对应：

| 浏览器 | 自带安装入口（英文界面） | 程序化提示 |
|---|---|---|
| Chrome，Android | 底部的 mini-infobar，文字为 "Add `<应用名>` to Home screen"，以及三点菜单中的 "Install app" | `beforeinstallprompt` + `prompt()` |
| Chrome 与 Edge，桌面端 | 地址栏右端的安装图标；菜单项 "Install `<应用名>`…"（Chrome）或 Apps › "Install this site as an app"（Edge） | `beforeinstallprompt` + `prompt()` |
| Safari，iOS 与 iPadOS | 共享菜单 › "Add to Home Screen"；iOS 26 的面板上有一个默认开启的 "Open as Web App" 开关 | 无 |
| Safari，macOS 14 及以后 | File › "Add to Dock" | 无 |
| Firefox for Android | 三点菜单 › "Install"（或 "Add to Home screen"） | 无 |
| Firefox 143 及以后，Windows | 地址栏图标，把站点固定为任务栏 web 应用 | 无 |

在 Chromium 上，时机不当的提示有明确的代价：`userChoice` 一旦兑现为 `"dismissed"`，事件对象就已耗尽，Chrome 会施加冷却期后才再次为该站点触发 `beforeinstallprompt`，所以每次访问页面最多只有一次放对位置的机会。把按钮放在用户完成一项任务之后（保存了表单、读完了文章、第二次会话），用更小的受众换这一次尝试落在意图最强的时刻；放在首屏则触达所有人，却被大多数人取消，这正是 web.dev 安装推广指南记录的权衡。

:::observed
在运行 iOS 26 的 iPhone 上（英文界面），Safari 共享菜单列出 "Add to Home Screen"，确认页显示一个默认开启的 "Open as Web App" 开关，见 WebKit 的 Safari 26.0 发布文章；因此站点不再需要 manifest 的 `display` 值，主屏幕条目也能以无浏览器界面的方式打开。在 Chrome for Android（英文界面）中，被 `preventDefault()` 压下的那条横幅文字为 "Add `<应用名>` to Home screen"，其中应用名在存在 `short_name` 时取 manifest 的 `short_name`。
:::

## 示例

两个示例都运行在页面中，共用一个回答"是否已安装"的辅助函数。

### 只在任务完成后显示安装按钮

事件到达时保存，但按钮由任务完成的处理器显示，而不是由事件显示。`appinstalled` 监听器覆盖用户从浏览器自带入口完成的安装。

```js
let deferred = null;
const button = document.querySelector('#install');

window.addEventListener('beforeinstallprompt', (event) => {
  event.preventDefault();
  deferred = event;                       // 先保存；此时不显示任何东西
});

function onTaskCompleted() {
  if (deferred && !isInstalled()) button.hidden = false;
}

button.addEventListener('click', async () => {
  const { outcome } = await deferred.prompt();
  deferred = null;
  button.hidden = true;
  if (outcome === 'accepted') analytics.track('install_accepted');
});

window.addEventListener('appinstalled', () => {
  deferred = null;
  button.hidden = true;
});

function isInstalled() {
  return matchMedia('(display-mode: standalone)').matches || navigator.standalone === true;
}
```

如果任务完成前事件一直没来（Chrome 的参与度启发式未满足，或浏览器不是 Chromium），`deferred` 保持 `null`，按钮保持隐藏，这是正确的行为：一个 `prompt()` 无法兑现的可见按钮比没有按钮更糟。

### 判断平台路径，用说明代替按钮

没有事件的地方，页面仍可以告诉用户如何安装，并且一旦应用以 standalone 运行就必须停止。

```js
function installRoute() {
  if (matchMedia('(display-mode: standalone)').matches || navigator.standalone === true) {
    return null;                                            // 已安装：什么都不说
  }
  if ('onbeforeinstallprompt' in window) return 'event';   // Chromium：等事件
  const ua = navigator.userAgent;
  if (/iPhone|iPad/.test(ua)) return '点按"共享"，然后选择"添加到主屏幕"。';
  if (/Macintosh/.test(ua) && /Safari/.test(ua) && !/Chrome/.test(ua)) return '选择"文件"，然后选择"添加到程序坞"。';
  if (/Android/.test(ua) && /Firefox/.test(ua)) return '打开菜单，然后选择"安装"。';
  return null;                                              // 未知：宁可不显示，也不要猜
}

const route = installRoute();
if (route && route !== 'event') showHint(route);
```

这里的 user agent 判断只用于挑选说明文字，从不用于判断能力；能力来自 `'onbeforeinstallprompt' in window` 与 `display-mode`。未知浏览器走 `null` 分支，避免让用户去找一个并不存在的菜单项。

## 另请参阅

- [How to provide your own in-app install experience](https://web.dev/articles/customize-install)（web.dev）
- [Patterns for promoting PWA installation](https://web.dev/articles/promote-install)（web.dev）
- [WebKit features in Safari 26.0](https://webkit.org/blog/17333/webkit-features-in-safari-26-0/)（webkit.org）
- [beforeinstallprompt 事件](/zh/reference/installation/install-prompt/)
- [可安装性条件](/zh/reference/installation/installability-criteria/)
- [iOS 添加到主屏幕](/zh/reference/installation/ios-add-to-home-screen/)
- [桌面端安装](/zh/reference/installation/desktop-install/)