# icons 清单成员

> icons 列出浏览器与操作系统用于已安装 PWA 的图像资源，含 src、sizes、type 与 purpose；它决定 Chromium 的可安装性以及 Android 上的 maskable 形状。

`icons` 是一个图像资源数组，用来在主屏幕、任务切换器、安装提示和系统设置中代表已安装的应用。每个
条目指定一个文件，并可选地给出它提供的尺寸、MIME 类型以及适用的 `purpose`。浏览器为每个场景挑选
合适的条目；作者无法指定哪个图标用在哪里。

Chrome 39、Edge 79、Samsung Internet 4.0、Android 版 Firefox 152、iOS 上的 Safari 15.4 以及 macOS
上的 Safari 17 读取该成员（BCD `html.manifest.icons`）。桌面版 Firefox 157 不从清单安装 Web 应用，
所以该成员在那里没有消费者。iOS 上的 Safari 只在页面没有 `<link rel="apple-touch-icon">` 时才读取
`icons`；两者同时存在时以 link 元素为准（WebKit 博客，Safari 15.4）。

## 成员

- **类型**：图像资源对象数组。`src`（字符串 URL，必填）相对清单 URL 解析，而不是相对页面 URL。
  `sizes`（字符串）是以空格分隔的 `<宽>x<高>` 列表，可缩放格式写 `any`。`type`（字符串）是 MIME
  类型。`purpose`（字符串）是以空格分隔的列表，取自 `any`、`maskable`、`monochrome`，默认为 `any`。
- **默认值**：空数组。没有可用图标的浏览器会退回自己的字母图块或通用图标；Chromium 则拒绝提供安装。
- **示例值**：`[{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" }]`。

Chromium 的可安装性检查需要一个至少 144 px 的 PNG、SVG 或 WebP 图标，`sizes` 必须设置，`purpose`
要么缺省要么包含 `any`。没有符合条件的图标时，DevTools > Application > Manifest 会在
**Installability** 下列出 `Manifest does not contain a suitable icon - PNG, SVG or WebP format of
at least 144px is required, the sizes attribute must be set, and the purpose attribute, if set,
must include "any".`（Chromium `installable_logging.cc`）。`sizes` 解析不出任何尺寸的条目会被解析器
丢弃，消息为 `found icon with no valid size.`；下载成功但无法解码的图标会以
`Downloaded icon was empty or corrupted` 阻止安装。

`purpose: "maskable"` 标记一个画满整幅的图标，让操作系统能把它裁成自己的形状（Android 自适应图标、
Windows 磁贴）。规范把安全区定义为以图标中心为圆心、半径为图标较短边 40% 的圆；圆外的部分都可能
被裁掉（w3.org，"maskable"）。maskable 图标不能替代 `any` 图标：用户代理应只按声明的用途使用图标，
所以只含 `maskable` 条目的清单会让 Chromium 找不到可安装性图标。图标抓取受文档 `img-src` CSP 指令
约束，不在允许列表内的跨源图标永远不会被请求。

:::observed
Chrome 155（macOS 26，英文界面）的 DevTools > Application > Manifest，**Icons** 一节：声明为
`"sizes": "192x192"` 但 PNG 实际为 180 px 见方的条目会带上警告
`Actual size (180×180)px of icon does not match specified size (192×192)px`；`"purpose": "any maskable"`
的条目带有 `Declaring an icon with 'purpose' of 'any maskable' is discouraged. It is likely to look
incorrect on some platforms due to too much or too little padding.`；勾选
**Show only the minimum safe area for maskable icons** 后，每个 maskable 预览都被裁成 40% 的圆。
:::

## 示例

下面的清单从 Chromium 接受的最小集合推进到同时覆盖 Android 遮罩与单色场景的集合；最后一个示例说明
没有运行时 API 时该核对什么。

### 覆盖 Chromium、Safari 与 Android 的完整图标集

两个 PNG 尺寸满足 Chromium 的安装门槛与 Android 启动画面，单独的 maskable 文件覆盖自适应图标，
单色 SVG 用于通知角标和主题化图标。每个条目都设置了 `type`，浏览器可以不嗅探字节就跳过不支持的格式。

```json
{
  "icons": [
    { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
    { "src": "/icons/icon-512-maskable.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" },
    { "src": "/icons/icon-mono.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "monochrome" }
  ]
}
```

在 Android 16 上，启动器把 maskable 文件放进设备的图标形状里，启动画面用 512 px 的 `any` 图标；
在 iOS 26 上，Safari 取最大的 `any` PNG 并自行套用 iOS 圆角遮罩，忽略 maskable 条目。

### 一个经得起裁切的 maskable 图标

maskable 文件是独立素材，不是给 `any` 图标加内边距得来的。标志保持在中心圆内（半径为宽度的 40%，
512 px 时直径为 410 px），背景一直不透明到边缘，因为被裁掉的区域会以操作系统的形状透出来。

```html
<!-- 不用真机预览裁切：clip-path 就是规范里的安全区。 -->
<img src="/icons/icon-512-maskable.png" width="256" height="256"
     style="clip-path: circle(40% at 50% 50%)" alt="仅显示安全区的 maskable 图标">
```

如果标志在这个预览里碰到了裁切边缘，它在圆形 Android 启动器和 Windows 11 开始磁贴上就会被切掉；
Maskable.app（maskable.app）能用 Android 出厂的每种形状渲染同一份素材。

### 没有运行时 API 时核对已发布的图标集

没有任何 JavaScript API 能报告浏览器选了哪个图标。可靠的做法是自查页面链接的那份清单，这同时也
充当特性检测：缺少 `<link rel="manifest">` 或页面没有 `fetch` 时返回 `null` 而不是抛错。

```js
async function declaredIcons() {
  const link = document.querySelector('link[rel="manifest"]');
  if (!link || !('fetch' in window)) return null; // 这里没有可检查的东西。
  try {
    const manifest = await fetch(link.href).then((r) => r.json());
    return Array.isArray(manifest.icons) ? manifest.icons : [];
  } catch {
    return null; // 清单不可达或格式错误。
  }
}

const icons = await declaredIcons();
if (icons === null) {
  console.info('Manifest not readable; skipping icon audit.');
} else {
  const has = (size) => icons.some((i) => String(i.sizes ?? '').split(' ').includes(size));
  const maskable = icons.some((i) => String(i.purpose ?? '').split(' ').includes('maskable'));
  console.table({ has192: has('192x192'), has512: has('512x512'), maskable });
}
```

一个 `"sizes": "any"` 的可缩放 SVG 条目单独就能过 Chromium 的 144 px 门槛，但 iOS 26 上的 Safari
不会把 SVG 用作主屏幕图标，所以旁边至少保留一个 PNG。

## 另请参阅

- [可安装性条件：PWA 如何才能被安装](/zh/reference/installation/installability-criteria/)
- [Chrome 与 Android 上的 PWA（WebAPK、TWA）](/zh/reference/platforms/chrome-android/)
- [iOS 添加到主屏幕：iOS PWA 安装](/zh/reference/installation/ios-add-to-home-screen/)
- [screenshots 清单成员](/zh/reference/manifest/screenshots/)
- [Web Application Manifest: icons member](https://www.w3.org/TR/appmanifest/#icons-member)（w3.org）
- [Adaptive icon support in PWAs with maskable icons](https://web.dev/articles/maskable-icon)（web.dev）
- [New WebKit Features in Safari 15.4](https://webkit.org/blog/12445/new-webkit-features-in-safari-15-4/)（webkit.org）