在 Google 上搜任何技术问题,看每条结果站点名旁边的小图标,那就是站点的 favicon。Google 从 2019 年起在移动端搜索结果里显示 favicon,现在结果中的站点名旁都会显示(Google 搜索中心)。没有可辨识 favicon 的站点在这里看起来像被弃置;有一个清晰、符合品牌的 favicon,就显得用心。这个小标记出现在你的域名旁边,正是用户决定点不点的地方。

打开 Favicon 生成器 →

这枚小图标,也是少数几样能横跨浏览器标签、收藏栏、操作系统任务栏、iOS 主屏、Android PWA 安装、RSS 阅读器、桌面快捷方式的视觉元素。每一个场景需要的文件略微不同 — 尺寸不同,有时格式不同,有时还要自己的 manifest 条目。本文讲清现代 favicon 套件到底长什么样、为什么单一 favicon.ico 已经不够、每个文件在运行时承担什么角色,以及 ZeroTool 生成器如何在浏览器里把这一整套打好包,全程不上传任何字节。

现代浏览器到底请求了哪些文件

浏览器请求哪些文件,取决于页面里的 <link> 标签和用户的操作。打开自己网站的 DevTools 刷新一次,看 Network 面板:桌面浏览器通常只请求它为标签页选中的那一个图标。三个入口是:

请求用途备注
/favicon.ico页面没有声明图标时,浏览器回退到这个路径多尺寸容器;保留它,给只会尝试根路径的工具和爬虫用
<link rel="icon"> 中 href 指向的图标签页、书签、历史记录浏览器按 sizes 和 type 在候选中挑选;HTML 标准把选择交给浏览器
<link rel="apple-touch-icon"> 中 href 指向的图iOS Safari「添加到主屏幕」标准尺寸 180×180

在 Android Chrome 上把站点装成 PWA,请求列表会变长:

请求用途
/site.webmanifest(或 manifest.json)Chrome、Edge、Brave、Samsung Internet — 任何能装 PWA 的浏览器
manifest icons 数组里声明的 192×192 与 512×512 PNGAndroid 主屏图标、启动画面、Android 分享面板

把图标标成 purpose: "maskable",Android 会按启动器套上圆形或圆角方形遮罩,让图标在启动器网格里看起来像原生应用。maskable 图标的可见内容必须落在安全区内:以图标尺寸 40% 为半径的圆(每边约留 10%),否则会被遮罩裁掉边缘(web.dev)。web.dev 不建议一个文件同时写 "any maskable":为遮罩留了边距的图标在按原样显示的地方会显得偏小,应分别提供 any 和 maskable 两个文件。

还有那条长尾。Windows 8 和 10 的固定网站磁贴读取 browserconfig.xml,Windows 11 已经没有动态磁贴。旧版 Safari 用 <link rel="mask-icon"> 声明的单色 SVG 绘制固定标签页。旧版 Android Chrome 用 apple-touch-icon 做主屏快捷方式。这些解释了为什么有的生成器会吐出二十多个文件。

为什么单一 favicon.ico 已经不够

历史上的默认做法是把一个 ICO 放到站点根目录,让浏览器自己挑。当显示器最大 1280×1024、标签页只有 16 像素方块时,这够用。两件事破坏了这个默认:

  1. Retina 显示器。 浏览器把 16×16 的 ICO 放大到 32 逻辑像素显示在 2× 屏上时会糊。<link rel="icon" type="image/png" sizes="32x32"> 让浏览器按设备像素比挑对的资源。
  2. 标签页之外的操作系统画布。 iOS 主屏图标、Android 主屏图标、PWA 启动画面,各自从你的套件里取特定尺寸,都不要 16 像素的 ICO。它们要 180、192、512,外加一份告诉它们谁是谁的 manifest。

修法就是现代套件:兼容用的小 ICO、给现代 <link rel="icon"> 链路用的若干 PNG 尺寸、给 iOS 的 apple-touch-icon,以及带 PWA 图标的 webmanifest。

完整文件清单与各自的运行时角色

ZeroTool 生成器输出的就是这十一个文件,以及它们在运行时被谁用:

文件尺寸用在哪里
favicon.ico16+32+48 多尺寸浏览器标签页:2026-10-02 用 Chromium 152(2× 屏)加载本工具的 HTML 片段,只请求了这个文件;页面没有声明图标时浏览器也会请求根目录的它
favicon-16.png16×16HTML 片段里声明了它;同时有 ICO 链接时,上述实测中 Chromium 没有请求它
favicon-32.png32×32同上;只声明 PNG、不声明 ICO 时,Chromium 在 2× 屏上请求的是它
favicon-48.png48×48HTML 片段里声明了它;Google 搜索建议图标大于 48×48
favicon-64.png64×64HTML 片段没有引用;需要时自己加 <link>
favicon-96.png96×96HTML 片段没有引用
favicon-128.png128×128HTML 片段没有引用
apple-touch-icon.png180×180iOS Safari「添加到主屏幕」
android-chrome-192.png192×192通过 PWA 安装后的 Android 主屏图标(manifest 图标)
android-chrome-512.png512×512Android 启动画面等大尺寸场景
site.webmanifesttext声明站点名称、start_url、display、主题色与 192/512 PNG(purpose: "any";包里没有 maskable 图标,见上文)

十一个文件。包的总大小取决于素材:扁平的 emoji 和简单 SVG 比照片压缩得好得多。

ICO 文件格式怎么工作

favicon.ico 是套件里最古怪的文件,因为它早于 PNG 出现 — 它生于 Windows 3.0,最初承载的是与设备无关位图(DIB / BMP)帧。如今多数生成器改用 PNG 压缩条目,Windows Vista 之后的资源管理器、IE 11 以及之后的所有浏览器都原生支持。PNG 压缩条目更小、保留 alpha 通道,格式也直白。

一个 ICO 文件是这样的:

ICONDIR (6 字节)
├── reserved (2 字节,零)
├── type (2 字节,1 = ICO,2 = CUR)
└── count (2 字节)

ICONDIRENTRY × count (每条 16 字节)
├── width (1 字节,0 表示 256)
├── height (1 字节,0 表示 256)
├── colorCount (1 字节,32 位时填零)
├── reserved (1 字节,零)
├── colorPlanes (2 字节,1)
├── bitsPerPixel (2 字节,32)
├── imageSize (4 字节)
└── imageOffset (4 字节 — 在 ICO 文件里的绝对偏移)

(各图像负载按顺序拼接在目录后面)

对 PNG 条目而言,imageSize 是一个完整 PNG 文件(含自身的 IHDR / IDAT / IEND 块)的大小,imageOffset 指向这个 PNG 的第一个字节。ICONDIRENTRY 里的宽高要和 PNG 自身的宽高一致:读取方在解码之前就是靠目录挑选条目的。

ZeroTool 生成器把 16、32、48 三个 PNG 打进 ICO,没装更大的尺寸:需要更多像素的浏览器会从 <link rel="icon"> 的 PNG、apple-touch-icon 和 manifest 里拿。如果你还想让这个 ICO 当 Windows 桌面图标用(可用到 256×256),另做一个包含该尺寸的 ICO。

ZIP 容器怎么工作

浏览器需要一种方法一次给用户十一个文件而不是触发十一次下载,所以生成器把它们打成 ZIP。ZIP 几十年来一直是默认容器,因为每个操作系统都不需要第三方工具就能打开。ZIP 内部分层:一系列「local file header + 负载」的序列,接一份「central directory」列出每条目,最后一条「end-of-central-directory」记录告诉读取方 central directory 从哪儿开始。

每条目的负载有两种编码方式:STORED(不压缩)或 DEFLATE。PNG 已经压缩过,再走一遍 DEFLATE 收益很小。ZeroTool 生成器所有条目都用 STORED,不在页面里带 DEFLATE 实现。

CRC-32 是 ZIP 规范里唯一不能跳过的部分。每条目都必须带未压缩字节的 32 位 CRC。生成器在脚本启动时预计算一张 256 项查找表,对每个文件的字节走一遍即可。文件名在 General Purpose Bit Field 的 bit 11 上声明为 UTF-8,这样在 macOS Finder、Windows 资源管理器和 7-Zip 里非 ASCII 名都能正确显示。

Canvas 管线

每个 PNG 的绘制都走相同的五步:

// 一个目标尺寸的伪代码
const canvas = createCanvas(size, size);
const ctx = canvas.getContext('2d');

if (shape !== 'square') {
  drawShapePath(ctx, shape, size);
  ctx.clip();           // 圆角 / 圆形的 alpha 蒙版
}
if (bgMode === 'color') {
  ctx.fillStyle = bgColor;
  ctx.fillRect(0, 0, size, size);
}
const inner = size * (1 - padding * 2);
drawSourceCentered(ctx, source, size, inner);  // emoji / text / image / svg
const pngArrayBuffer = await canvasToPng(canvas);

canvas.toBlob('image/png') 干所有重活 — 缩放、alpha 编码。输出是完整的 PNG 负载,含 IHDR、一个或多个 IDAT 块和 IEND。IDAT 里的 zlib 压缩级别由浏览器决定,所以不同浏览器生成的文件大小略有差异。

有三种失败模式值得了解:

  1. canvas 被污染。 canvas 一旦被污染,toBlob 会抛 SecurityError。生成器会捕获它,提示你先把 SVG 里的外部资源内联。
  2. 缺彩色 emoji 字体。 有些 Linux 系统没有装彩色 emoji 字体,canvas 会画出替代字形(常见是空框),这个框会进入每一张 PNG。生成器不会检测这种情况;看一眼预览,或改用图片 / SVG 输入。
  3. 大图的内存压力。 生成器依次渲染九个尺寸,每个 toBlob 都 await,不会同时持有九个 canvas。

SVG、emoji 与跨平台一致性问题

最微妙的输出差异来自彩色 emoji 字体。macOS 自带 Apple Color Emoji,Windows 自带 Segoe UI Emoji,Android 和许多 Linux 桌面用 Noto Color Emoji。同一个码点(比如 🚀)在每种字体里画法各不相同。

如果你在 macOS 上生成 favicon,访客从 Windows 访问也只看到你生成的那个 favicon — 字节已经烤进 PNG。Canvas 渲染器把 emoji 在开发者机器上呈现的样子捕获下来,然后把这些像素发给所有访客。所以选一台机器,生成一次,结果对之后所有访客都一致。一致性真正成问题的地方在开发者自己的迭代:六个月后在另一个操作系统上重新生成,会得到一个略有差异的 favicon。

要追求跨机器像素级一致,就切到 Image 或 SVG 输入。SVG 矢量图按其自身定义确定性地光栅化,与操作系统的字体栈无关。代价是 SVG 必须自包含 — 不能引远程资源的 <image href>、不能用外部 <use> 片段、不能用远程 @font-face。粘贴前把所有外部依赖都内联。

HTML 片段为你做了什么

生成器输出的片段如下,放进 <head>:

<link rel="icon" type="image/x-icon" href="/favicon.ico">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png">
<link rel="icon" type="image/png" sizes="48x48" href="/favicon-48.png">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="manifest" href="/site.webmanifest">
<meta name="theme-color" content="#ffffff">

几个微妙之处:

  • HTML 标准让浏览器根据 sizes 和 type 在所有 <link rel="icon"> 候选中自行挑选,没有规定优先顺序。把 ICO 放第一是惯例,不是要求。
  • <meta name="theme-color"> 控制移动端 Safari、Chrome 地址栏,以及已安装 PWA 的标题栏颜色。它不是 favicon,但因为住在同一个 head 段、共用品牌色决策,所以与 favicon 套件一起出现。
  • 文件名 site.webmanifest 是惯例;不少教程用 manifest.json。两者都可以。沿用项目其他位置已用的那个就好。
  • iOS Safari 的主屏图标在有 apple-touch-icon 时用它。Android Chrome 给已安装的 Web 应用用 manifest 里的图标。

ZeroTool 与其他生成器的对比

RealFaviconGenerator 是老牌参考工具,平台专属选项更多,还能检查现有站点的 favicon 配置。需要这些选项时用它。

favicon.io 在思路上最接近 ZeroTool:输入简单(emoji、文字、图片),输出一个带 webmanifest 的小包。

ZeroTool 生成器输出十一个文件、一份 webmanifest 和一段可直接粘贴的 HTML,全部在浏览器里渲染,不上传源图。

部署前 checklist

部署之前,请走一遍这几步:

  1. 把十一个文件放到站点根目录。 找不到图标声明的浏览器和爬虫会请求根目录的 /favicon.ico,HTML 片段和 manifest 也用的是根路径。
  2. 把 HTML 片段贴进 <head>。 PNG 与 apple-touch-icon 只有在页面用 <link> 声明后才会被使用。
  3. 在生成器里填写站点名称。 它写进 site.webmanifest 的 name(短名称写进 short_name);留空时生成器不写这两个字段,Chrome 不会提示把网站安装为应用。
  4. 用 DevTools 的 Application 面板测。 Application → Manifest 会显示解析后的 manifest、解析出的图标和错误。勾选「Show only the minimum safe area for maskable icons」可以预览 maskable 裁切。
  5. 检查 iOS 路径。 在真机 iPhone(或 iOS 模拟器)上「添加到主屏幕」。iOS 会自己加圆角,不保留透明度,所以 apple-touch-icon 要用不透明背景。
  6. 检查 Google 搜索里的 favicon。 Google 要求正方形、至少 8×8,建议大于 48×48(Google 搜索中心)。Google 重新抓取首页后才会更新;Search Console 没有 favicon 报告。
  7. 品牌变了就重做。 把 favicon 套件当作一个构建产物,而不是一次性资产。把源文件(喂给生成器的 SVG 或高分辨率 PNG)纳入版本控制,标识或主题色变更时从同一源文件再生成。

隐私说明

因为整条管线都跑在浏览器里,你的源图任何版本都不离开本机。这件事比听起来重要:给一个未发布的产品做 favicon 时,用会上传的生成器就等于把品牌标记交给了它。ZeroTool 把图留在本地。打开 DevTools → Network 点 Generate:没有任何请求携带图片,统计事件只记录工具名和动作。

延伸阅读

ZeroTool 站内相关工具:SVG 转 PNG 工具、WebP 转换器、图片转 Base64 工具、二维码生成器。