动态 Favicon 实战:让浏览器标签页动起来(含可运行 Demo)

Favicon.im

读这篇文章的时候,你大概已经注意到这个标签页顶上有东西在动了。这就是动态 favicon —— Gmail 用它显示新邮件计数,Discord 用它显示通知红点,原理一样,能玩出的花样几乎只看你能在 canvas 上画出什么。

这篇文章里嵌了一个 demo,你可以点着玩。点一个按钮,看看你的浏览器标签页变了没有。没有截图,也没有内嵌视频 —— 你现在看到的这个 favicon 就是 demo 本身。

为什么要做动态 Favicon?

说实话,大多数网站不该做。每个标签页里都转着圈的图标用不了多久就让人烦,还吃 CPU。但下面这几种场景确实值得:

  • 加载或处理状态。 长时间的上传、导出、构建。用户切到别的标签等结果,动态 favicon 能告诉他活儿还在干。
  • 通知小红点。 新消息、@提醒、告警。一个会轻微脉动的红点比静态的更容易被注意到。
  • 实时数据流。 交易看板、监控工具、比赛比分 —— 标题栏不够用的时候。
  • 品牌瞬间。 节日转转、上线纪念日。点到即止。

如果你的场景不在以上几类,就别搞动画了。一个干净的静态 SVG favicon 在体积、深色模式、电池续航上都更划算。

实时 Demo:现在就试

挑一个动画。然后看看你浏览器标签页 —— 顶上那个小图标正在被重绘。

8 倍放大预览

真实的 favicon 只有 16×16 像素,太小看不清细节,所以左边这个框用最近邻缩放放大 8 倍同步显示同一张 canvas。

状态:空闲

整套动画循环现在完全跑在 Web Worker 里,做法和 Aymkdn 库的 favicon_worker.js 一致。每隔 20ms(50fps),worker 在自己的 OffscreenCanvas 上画一帧,通过 convertToBlob + FileReader 导出成 data URL,再把字符串发回主线程。主线程只做一件事:把那串字符赋值给 faviconLink.href。这就是为什么图标动得跟 GitHub 那个 demo 一样顺滑。

想看这套方案在真实产品里跑起来是什么样?Random Picker Wheel 就用动态 favicon 同步它的转盘——转盘式 UI 正是动画图标真正贴合产品的少数场景之一。打开页面,转一下转盘,看看标签页上的图标也跟着一起转。

它到底是怎么动起来的

整个技术就三步:

// 1. 拿到或者新建一个 favicon 的 link 元素
let link = document.querySelector('link[rel~="icon"]');
if (!link) {
  link = document.createElement('link');
  link.rel = 'icon';
  document.head.appendChild(link);
}

// 2. 在一个隐藏 canvas 上画一帧
const canvas = document.createElement('canvas');
canvas.width = 32;
canvas.height = 32;
const ctx = canvas.getContext('2d');

function drawFrame(t) {
  const scale = 0.5 + 0.5 * Math.abs(Math.sin(t / 400));
  ctx.clearRect(0, 0, 32, 32);
  ctx.fillStyle = '#ef4444';
  ctx.beginPath();
  ctx.arc(16, 16, 14 * scale, 0, Math.PI * 2);
  ctx.fill();

  // 3. 把 canvas 导出成 data URL,赋值给 favicon
  link.href = canvas.toDataURL('image/png');

  requestAnimationFrame(drawFrame);
}

requestAnimationFrame(drawFrame);

差不多 15 行就够做一个会脉动的小红点。再花哨的样式,无非就是在 canvas 上画不同的图形。

浏览器兼容性的真实情况

这块开始不太好看。各家浏览器更新 favicon 的勤快程度差别很大:

  • Firefox:标签页没在前台也照常顺滑播放,业界标杆。
  • Chrome / Edge:当前标签页活动时正常播放。一旦切走,requestAnimationFrame 会被节流到大约每秒一帧,动画看起来就慢甚至卡死。
  • Safari:当前标签页在前台时能动,但更新间隔有时拖很久,别指望流畅。

不过对最常见的用法 —— 通知红点、进度状态 —— 这其实不是问题,本来也不需要每秒更新好几次。60fps 的丝滑旋转大多数时候只是好看而已。

后台标签页的 Web Worker 技巧

上面那个"GitHub 风格翻转"按钮,是把 Aymkdn/animated-favicon 这个库的招牌动画原样搬过来:图标 A 停 3 秒,按 cosine 把宽度压扁到零,切换成图标 B,再把宽度展开。背后的算式直接来自库里的 favicon_worker.jswidth = canvas.width * Math.abs(Math.cos(progress * Math.PI)),当 progress 越过 0.5 的时候,第二张图开始接管。

Aymkdn 的库还多走了一步:把这个循环跑在 Web Worker 里。Worker 不会因为标签页切到后台而被节流,所以动画还会继续,再加上 OffscreenCanvas,整个渲染过程根本不需要碰 DOM。

代码结构大致是这样:

// 主线程
const worker = new Worker('favicon-worker.js');
worker.onmessage = (e) => {
  if (e.data.type === 'updateFavicon') {
    document.querySelector('link[rel~="icon"]').href = e.data.dataUrl;
  }
};
worker.postMessage({ type: 'init', images: ['icon-a.png', 'icon-b.png'] });

// favicon-worker.js 里面
const canvas = new OffscreenCanvas(16, 16);
const ctx = canvas.getContext('2d');
// ...画一帧...
const blob = await canvas.convertToBlob();
const reader = new FileReader();
reader.onloadend = () => self.postMessage({ type: 'updateFavicon', dataUrl: reader.result });
reader.readAsDataURL(blob);

如果你的应用属于"用户经常挂在后台"这种 —— 聊天客户端、构建看板、监控工具 —— 那这一步是值得做的。否则主线程版本写起来更简单,效果也够用。

几条值得记住的细节

用 16×16 或 32×32,别更大。 反正最终也只显示这么大,更大的 canvas 只会让 data URL 更长、每帧 CPU 占用更高。32×32 配上锐利像素是甜点档。

导出成 PNG,别用 ICO。 canvas.toDataURL('image/png') 是唯一能稳定工作的格式。别想着自己手写 ICO 编码。

停下时记得还原原 favicon。 在动画启动前先把 link.href 存起来,等操作结束或者 beforeunload 时把它复原。一直挂着半坏的动画看起来很 bug。

别让动画无休止地转。 即使是很轻微的脉动,在手机上都会耗电。加载结束、用户读完通知、标签页失焦时就停下来。

简单场景直接放静态图。 只想显示"1 条未读消息",那就把 favicon 切成一个静态红点版本,不用动画。大多数用户用动态 favicon 干的活儿其实都用力过猛。

什么时候该用它

动态 favicon 适合的场景:

  • 动画反映的是用户真正关心的状态(上传、处理、新消息)
  • 页面是那种容易被挂在后台的类型
  • 静态角标或仅仅改一下标题栏不足以传达同样的信息

不适合的场景:

  • 纯粹是装饰
  • 标签页一打开就一直在转
  • 主要受众跑在 Safari 或移动端,那边几乎没法用

把上面这个 demo 拿过去,把颜色和图形换成你的品牌风格,就能上线。完整源码就在这一页里,View Source 直接复制即可。

参考资料

  1. Aymkdn/animated-favicon(GitHub) —— 基于 Web Worker 的动态 favicon 库,标签页切到后台也会继续动
  2. The Making of an Animated Favicon —— CSS-Tricks —— Chris Coyier 写的 canvas 转 favicon 技巧拆解
  3. How to animate a favicon? —— Stack Overflow —— 经典讨论帖,多种方案和兼容性注解
  4. OffscreenCanvas —— MDN —— 让 Web Worker 动态 favicon 模式成立的关键 API
检查您的 Favicon

使用 favicon.im 快速检查您的 favicon 是否配置正确。我们的免费工具确保您网站的 favicon 在所有浏览器和设备上正确显示。

免费公共服务

Favicon.im 是一个完全免费的公共服务,受到全球开发者的信赖。

15M+
每月 Favicon 请求数
100%
永久免费