アニメーション Favicon 実践ガイド:ブラウザタブを動かす(ライブデモ付き)
このページを読んでいるあいだ、タブの上で何かが動いているのに気付いたかもしれません。それがアニメーション favicon です。Gmail が新着メール数を見せるのに使っているのと同じ仕組みで、Discord が通知ドットに使っているのと原理は変わりません。canvas に描けるものなら、ほぼ何でもできます。
この記事にはあなたが触れるデモを埋め込んであります。ボタンをクリックすると、実際にブラウザのタブが変わります。スクリーンショットでも埋め込み動画でもなく、いま見えている favicon そのものがデモです。
そもそもなぜ favicon をアニメーションさせるのか?
正直なところ、ほとんどのサイトはやらない方がいいです。タブごとにくるくる回るアイコンはすぐにうるさく感じますし、CPU も食います。それでも本当に役に立つ場面はあります:
- 読み込み・処理中の状態。 長時間のアップロード、エクスポート、ビルド。ユーザーが他のタブに切り替えて待っているとき、アニメーション favicon は「まだ動いていますよ」と伝えてくれます。
- 通知バッジ。 新着メッセージ、メンション、アラート。じわっと脈打つ赤ドットは、静止した赤ドットより気付かれやすいです。
- ライブデータ。 トレーディングダッシュボード、監視ツール、スポーツのスコア。タブのタイトルだけでは足りないとき。
- ブランド演出。 季節物、ローンチ当日のセレブレーションなど。やりすぎ注意。
これに当てはまらない場合は、アニメーションをやめましょう。良くできた静的な SVG favicon の方が、ファイルサイズ・ダークモード対応・バッテリー消費すべてで勝ちます。
ライブデモ:いますぐ試す
アニメーションを 1 つ選んでください。そしてブラウザタブの上を見てください。あの小さなアイコンがリアルタイムで描き換えられています。
実際の favicon は 16×16 ピクセルしかなく細部が見えづらいので、左の枠は同じ canvas を最近傍法で 8 倍にミラーリングしています。
状態:アイドル
アニメーションループ全体が Web Worker の中で動いている、という点が Aymkdn ライブラリの favicon_worker.js と同じやり方です。20ms ごと(50fps)に worker が自分の OffscreenCanvas に 1 フレーム描画し、convertToBlob + FileReader で data URL に書き出して、メインスレッドへ送り返します。メインスレッドはそれを faviconLink.href に代入するだけ。これが、GitHub のデモと同じくらい滑らかに動く理由です。
実際のサービスで動いている例も見てみますか?Random Picker Wheel は回転するルーレットに合わせて favicon をアニメーションさせています。ルーレットのような回転 UI は、動くアイコンが本当に製品に馴染む数少ないケースのひとつです。ページを開いてホイールを回すと、タブのアイコンも一緒に回るのが見えます。
実際にどう動いているのか
技術自体はたったの 3 ステップです:
// 1. favicon の link 要素を取得、なければ作成
let link = document.querySelector('link[rel~="icon"]');
if (!link) {
link = document.createElement('link');
link.rel = 'icon';
document.head.appendChild(link);
}
// 2. 隠しキャンバスに 1 フレーム描画
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が約 1 秒に 1 回まで節約され、アニメーションは遅く、または止まります。 - Safari:アクティブ時は動きますが、アイコンの更新間隔が長くなることがあり、滑らかさは期待できません。
ただ、よくある用途 —— 通知ドット、進捗状態 —— なら、毎秒何度も更新する必要は元からないので、これは大きな問題ではありません。60fps の滑らかなスピナーは多くの場合ただの装飾です。
バックグラウンドタブのための Web Worker の小技
上の「GitHub 風フリップ」ボタンは、Aymkdn/animated-favicon ライブラリの代名詞アニメーションをそのまま移植したものです:アイコン A を 3 秒表示、cosine で幅をゼロまで縮め、アイコン B に切り替え、また広げる。背後の数式はライブラリの favicon_worker.js から直接来ています:width = canvas.width * Math.abs(Math.cos(progress * Math.PI))。progress が 0.5 を超えると 2 枚目の画像が引き継ぐ仕組みです。
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');
// ...1 フレーム描画...
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 で戻します。半分壊れたアニメーションが残っているとバグっぽく見えます。
永久に動かさない。 弱いパルスでもモバイルではバッテリーを食います。読み込みが終わった、通知が読まれた、タブがフォーカスを失ったタイミングで止めましょう。
シンプルなケースは静止画で十分。 「未読 1 件」と表示したいだけなら、赤ドット入りの静止 favicon に切り替えれば済みます。アニメーションは多くの場合やりすぎです。
どんなときに使うか
アニメーション favicon が向いているのは:
- ユーザーが本当に気にしている状態(アップロード、処理、新着メッセージ)を反映するとき
- ページがバックグラウンドタブに置かれがちな種類のアプリ
- 静的バッジやタブタイトルだけでは伝えきれない情報があるとき
向いていないのは:
- ただの装飾
- タブを開いている間ずっと回り続ける
- 主要ユーザーが Safari やモバイルで、まともに動かない環境
上のデモを取り込んで、色や形を自社ブランドに合わせ替えれば本番投入できます。完全なソースはこのページに書いてあります。ソース表示でコピーしてどうぞ。
参考資料
- Aymkdn/animated-favicon(GitHub) —— Web Worker ベースのアニメーション favicon ライブラリ。タブが非アクティブでも動き続ける
- The Making of an Animated Favicon —— CSS-Tricks —— Chris Coyier による canvas → favicon テクニックの解説
- How to animate a favicon? —— Stack Overflow —— 複数のアプローチとブラウザサポートのメモが集まる定番スレ
- OffscreenCanvas —— MDN —— Web Worker でのアニメーション favicon を成立させる中心 API
favicon.im を使用して、ファビコンが正しく設定されているかすばやく確認できます。私たちの無料ツールは、すべてのブラウザとデバイスでウェブサイトのファビコンが正しく表示されることを保証します。
無料パブリックサービス
Favicon.im は世界中の開発者に信頼されている完全無料のパブリックサービスです。