# Badging API

> setAppBadge() 与 clearAppBadge() 如何在已安装应用图标上显示数字或圆点，哪些浏览器绘制它，TypeError 与无效情形，以及回退做法。

Badging API（`navigator.setAppBadge()` 与 `navigator.clearAppBadge()`，service worker 内的 `WorkerNavigator` 上同样暴露）在已安装 web 应用位于任务栏、Dock 或主屏幕的图标上显示一个数字或一个圆点。Chrome 81 与 Edge 81 在 Windows、macOS、ChromeOS 上绘制它；Safari 16.4 为 iOS 主屏幕 web 应用绘制，Safari 17 为 macOS Dock web 应用绘制；Android 上的 Chrome 84 与 Samsung Internet 13 只对已安装应用、且启动器支持角标时生效；Firefox 没有实现（BCD `api.Navigator.setAppBadge`）。

## 语法

```js
await navigator.setAppBadge();          // 一个圆点（"flag"），不带数字
await navigator.setAppBadge(contents);  // 一个数字；0 表示清除
await navigator.clearAppBadge();        // 移除角标

// service worker 内，例如在 push 处理器中
self.navigator.setAppBadge(count);
```

两个方法都返回 `Promise<void>`，在请求交给操作系统后兑现，而不是在角标可见时。Chromium 上不涉及权限提示；iOS 上只有用户允许了已安装应用的通知之后才会绘制角标。

## 参数

只有 `setAppBadge()` 接受参数。

| 名称 | 类型 | 说明 |
|---|---|---|
| `contents` | `unsigned long long`，可选 | 要显示的数字。`0` 清除角标，等同于 `clearAppBadge()`。省略时角标是一个不带数字的圆点。大数字如何显示由操作系统决定；规范把该值称为提示，允许平台缩写。 |

## 异常

`setAppBadge()` 在两种情形下拒绝或抛出，在另外两种情形下静默无效。

- `TypeError`：`contents` 为负数、小数，或超出 `[EnforceRange] unsigned long long` 的范围。类型转换发生在创建 Promise 之前，因此 Chromium 中该错误是同步抛出的：`Failed to execute 'setAppBadge' on 'Navigator': Value is outside the 'unsigned long long' value range.`。
- `InvalidStateError`：调用方文档不处于 fully active 状态（例如已脱离文档的 iframe），这是规范中两个方法的第一步。
- 无错误、无角标：应用未安装。Chromium 在普通标签页中兑现 Promise 但不绘制任何东西，所以角标只在安装后可见。
- 无错误、无角标：iOS 16.4 及以后，主屏幕 web 应用尚未获得通知权限（兼容性数据集备注）。调用兑现，图标保持原样。

Firefox 中 `navigator.setAppBadge` 为 `undefined`，未加保护的调用在触及 API 之前就会抛出 `TypeError`。

:::observed
在 Chrome 控制台（英文界面）中，`navigator.setAppBadge(-1)` 抛出 `TypeError: Failed to execute 'setAppBadge' on 'Navigator': Value is outside the 'unsigned long long' value range.`；而在普通标签页中 `await navigator.setAppBadge(3)` 兑现为 `undefined` 且不绘制任何东西；从 `/demo/#badging` 安装该演示应用后再执行同一调用，Dock 或任务栏图标上会显示 "3"。该报错文字是 Blink 对 `[EnforceRange]` 转换的标准信息；标签页中无效的行为与 Chrome 能力指南中"角标作用于已安装应用"的说明一致。
:::

## 示例

第一个示例运行在 service worker 中；第二个运行在页面中，并覆盖没有该 API 的浏览器。

### 把 push 事件里的未读数同步到图标

推送载荷若携带服务端的未读数，可以在用户打开应用之前就更新图标。`event.waitUntil()` 让 worker 存活到角标调用落定；在页面显示收件箱时调用 `clearAppBadge()`，可以保证图标与实际一致。

```js
// sw.js
self.addEventListener('push', (event) => {
  const data = event.data?.json() ?? {};
  const unread = Number.isInteger(data.unread) ? data.unread : 0;
  event.waitUntil(Promise.all([
    self.registration.showNotification(data.title ?? 'New message'),
    self.navigator.setAppBadge(unread),
  ]));
});

// page.js，在显示收件箱视图时
navigator.clearAppBadge?.();
```

在 Chromium 上只设角标而不显示通知是允许的，但在 iOS 上，不显示通知的 push 事件可能让应用失去推送订阅，所以上面的通知调用在 iOS 上不可省略。

### 检测支持并回退到文档标题

缺少 `setAppBadge` 时，唯一能跨浏览器承载计数的界面是标签页标题。下面的回退把计数作为前缀加到标题上，计数为零时去掉前缀。

```js
function showUnread(count) {
  if ('setAppBadge' in navigator) {
    return count > 0 ? navigator.setAppBadge(count) : navigator.clearAppBadge();
  }
  // 没有 Badging API（Firefox，或任何浏览器的普通标签页）：改用标题。
  const base = document.title.replace(/^\(\d+\) /, '');
  document.title = count > 0 ? `(${count}) ${base}` : base;
  return Promise.resolve();
}
```

标题回退只在标签页打开期间可见，这正是 Badging API 消除的限制；对 Firefox 用户来说，这仍然比什么都不显示更合适。

## 另请参阅

- [Badging API specification](https://w3c.github.io/badging/)（w3c.github.io）
- [Badging for app icons](https://developer.chrome.com/docs/capabilities/web-apis/badging-api)（developer.chrome.com）
- [Badging API 浏览器支持](/zh/compatibility/badging-api/)
- [通知操作与角标](/zh/reference/notifications/notification-actions-badge/)
- [Web Push](/zh/reference/notifications/web-push/)
- [iOS 添加到主屏幕](/zh/reference/installation/ios-add-to-home-screen/)