動的 favicon: バッジやリアルタイムのステータスを表示する
ブラウザのタブに表示されるあの赤いバッジは、favicon が再描画されているだけです。ビルドステップなしで実現する方法を紹介します。
なぜ GIF をそのまま使わないのか?
アニメーション GIF を favicon に設定することはできますが、サポートはまちまちです。Firefox はアニメーションさせます。Safari は最初のフレームだけを表示します。Chrome の挙動はバージョンによって変わってきました。装飾には十分ですが、実行時に GIF の表示内容を変えられないため、リアルタイムのバッジには不向きなツールです。
タブが開いている間に更新される件数やステータスには、JavaScript で画像を再描画する必要があります。canvas なら実際のピクセルバッファが手に入り、そこに描画して data URI として favicon のリンクに渡せます。
仕組み
- 1
ベースとなるアイコンを読み込む
通常の favicon 画像を小さな canvas、たいていは 32x32 に描画します。これはバッジを付けるものが何もないときにユーザーが目にするアイコンです。
- 2
上からバッジを描画する
件数があるときは、隅に小さな塗りつぶしの円を描き、その中に数字を描画します。32 ピクセルでも読めるように高コントラストを保ちましょう。
- 3
favicon の href を差し替える
favicon リンクの href を canvas の出力に設定します。ブラウザは新しい画像でタブをすぐに再描画します。
- 4
変化のたびに更新する
件数が変わるたびに描画と差し替えを再実行し、ゼロになったらプレーンなアイコンを描き直します。
差し替えを素の JavaScript で
function setFaviconBadge(count) {
const canvas = document.createElement("canvas");
canvas.width = 32;
canvas.height = 32;
const ctx = canvas.getContext("2d");
// draw your base icon first (an <img> you preloaded)
ctx.drawImage(baseIcon, 0, 0, 32, 32);
if (count > 0) {
ctx.fillStyle = "#e5484d";
ctx.beginPath();
ctx.arc(23, 9, 9, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = "#ffffff";
ctx.font = "bold 12px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(count > 9 ? "9+" : String(count), 23, 10);
}
let link = document.querySelector("link[rel~='icon']");
if (!link) {
link = document.createElement("link");
link.rel = "icon";
document.head.appendChild(link);
}
link.href = canvas.toDataURL("image/png");
}baseIcon は一度読み込んだ Image です。数字が変わるたびに setFaviconBadge(unread) を呼び出します。2 桁でも読めるよう、表示は 9+ で頭打ちにします。
よくある質問
ブラウザのタブに未読件数を表示するには?
アイコンと、数字入りのバッジを canvas に描画し、favicon リンクの href を canvas.toDataURL() に設定します。件数が変わるたびに再実行してください。通常の Web ページ向けに、タブ用の専用バッジ API はありません。
アニメーション GIF を favicon に使えますか?
使えますが、サポートは一貫していません。Firefox はアニメーションさせ、Safari は最初のフレームを表示し、Chrome はまちまちです。更新されるリアルタイムのバッジには、canvas を使って href を差し替えてください。
動的 favicon はパフォーマンスを損ないますか?
いいえ。32x32 の canvas への描画と data URI の設定は軽い処理です。タイマーではなく値が変わったときだけ描き直せば、コストは気になりません。