开发者 Tim Wehrle 做了一个前端小实验:把一段极小 HTML 内容写进网站 favicon 的 PNG 像素里,再让页面 JavaScript 读取这个图标、解码像素,并把内容渲染回页面。
这件事的重点不在“网站被塞进了图标”这个噱头,而在浏览器已有能力的边界:favicon 本质上是图片,图片由像素组成,RGB 通道可以承载字节。但这个 favicon 只保存网站内容,页面仍然需要一段 JavaScript bootstrap loader 才能启动解码。
favicon 被当成了一个微型字节容器
Wehrle 使用的 HTML payload 很小,只有 208 字节。为了知道有效内容在哪里结束,他在前面加了 4 字节长度头,总计 212 字节。
每个像素用 RGB 三个通道存 3 字节,因此需要至少 71 个像素。最终选择 9×9 PNG,一共 81 个像素,容量约 239 字节,使用率为 87%。示例图标看起来就是噪声图,并不需要伪装成正常图标。
| 项目 | 数值 | 判断 |
|---|---|---|
| HTML payload | 208 字节 | 只能放极简内容 |
| 长度头 | 4 字节 | 用于标记真实 payload 结束位置 |
| PNG 尺寸 | 9×9 像素 | 小于常见 favicon 尺寸 |
| 理论容量 | 约 239 字节 | RGB 三通道存储,空间极小 |
| 使用率 | 87% | 实验效率高,但规模不可扩展 |
这和传统 steganography 不完全是一回事。隐写术通常强调“藏在正常图像里不被察觉”,而这个 demo 的 favicon 可以直接变成彩色噪声;它更像把 PNG 当成原始字节容器,而不是做隐蔽通信。
解码依赖浏览器现成能力,也暴露了最大限制
读取过程并不复杂:浏览器先加载 favicon,JavaScript 把它绘制到 canvas,再通过 Canvas API 读取像素数据。随后脚本按 RGB 顺序重建字节数组,读取前 4 字节得到长度,用 TextDecoder 还原 UTF-8 HTML。
这套流程对前端开发者有启发:浏览器里的很多“资源类型”并没有被语义锁死。图标、图片、字体、音频文件,只要能被读取和解析,都可能在实验条件下承载额外数据。
但它的边界也很硬。没有 JavaScript,favicon 只是一个 PNG;没有 canvas 读像素,payload 取不出来;内容稍大,9×9 很快不够用。相比 data URL、内联脚本、Service Worker 缓存,甚至一个普通静态 HTML 文件,这种分发方式都更绕、更脆,也更难维护。
Wehrle 已经提供了 demo 页面:https://www.timwehrle.de/labs/favicon-site/ ,代码也放在 GitHub:https://github.com/timwehrle/favicon 。对想复现实验的开发者来说,真正值得看的不是网页效果,而是编码、长度头和像素读取这条链路。
受影响的是好奇的前端开发者,不是生产系统
这类实验最适合两类人:一类是学习浏览器底层行为的前端开发者,另一类是研究图像编码和 Web API 边界的技术读者。它可以作为教学材料,说明 TextEncoder、PNG 像素、canvas、TextDecoder 如何串起来。
生产团队不该把它当成方案。容量太小,启动还依赖额外脚本,缓存、跨源读取、图像处理链路都可能带来变量。原文也把结论说得很克制:不实用,只是在测试边界。
接下来真正可观察的不是“能不能做成产品”,而是类似实验会不会帮助开发者重新理解浏览器资源模型。前端工程里有很多默认概念——图标就是图标,图片就是图片。这个实验提醒人们,文件格式和浏览器 API 之间仍有不少灰缝可供学习,但灰缝不是捷径。
