🍣

Faviconは壮大なキャンバスだ

5 min read

ブラウザのタブに表示されるアイコンを操作しよう

なんで?

すべてはSushi Work(*´з`)

すてきなサービスができたものです(がんばれー)

タイマー系のWebアプリは通知してくれればタブを表示している必要はないのですが
”通知うざい”を解決する方法がなかなかありません(感じ方には大きな個人差があります)
集中して作業しているけど、あとちょっとのカウントと終わったときは教えてほしい・・・

そんなわがままを解決するよい方法はないものか

というわけで、アイコン変更するならついでにテキストも表示したらいいのでは?という思い付きからの検証コードを用意してみました
実装が超絶適当で本当に申し訳ないきもち

どんなものか

画面に表示されたボタンを押すと、faviconの上に数字が表示されてどんどんカウントダウンしていきます
ただそれだけです・・・

準備

*いろいろ説明は飛ばしますので本題からずれるところはダイジェストです

Typescriptでプロジェクトをつくる

  • 適当なフォルダでプロジェクト作ります
npm init
npm install --save-dev typescript
./node_modules/.bin/tsc --init
  • 必要な設定ファイルを編集します(雑ですいません)
    tsconfig.json、その他webpackやtask.json、launch.jsonなどは適宜いい感じに設定

VSCodeの設定をする

今回はHTMLをホストしてくれればいいので難しいことはせずに
Live Serverプラグインをインストール

コードを書く

index.ts

"use strict";
/**
 * faviconに残カウントを表示するクラス
 */
export default class FaviconChanger {
    private iconImage: HTMLImageElement;

    /**
     * コンストラクタ
     * レンダリング時に使用したIconオブジェクトを引き渡す(new Image())
     * 
     * @param iconImgElm 
     */
    constructor(iconImgElm: HTMLImageElement){
        this.iconImage = iconImgElm;
    }
    
    /**
     * 番号を指定してfaviconの数値を変える
     * 
     * @param remainingSeconds 
     */
    public changeIcon(remainingSeconds: number) {
        //アイコンがロードされていなければ処理しない
        if (!this.iconImage.complete) {
            return;
        }
        try {
            const canvas = this.makeCanvas();
            const ctx = canvas.getContext('2d');
            const txt = this.buildText(remainingSeconds);
            
            if(ctx != null){
                this.makeContext(ctx, txt);
            }

            this.changeFaviText(canvas);
        }
        catch (e) {
            console.log(e);
        }
    }

    /**
     * 変更するfaviconの領域用Canvasの作成
     */
    private makeCanvas(): HTMLCanvasElement{
        const canvas = document.createElement('canvas');
        //favicon size
        canvas.width  = 35;
        canvas.height = 35;
        return canvas;
    }

    /**
     * 表示するテキストの作成
     * @param counter 
     */
    private buildText(counter: number): string {
        var txt: string = '';
        if (counter > 0) {
            txt = counter > 99 ? '99+' : counter.toString();
        }
        return txt;
    }

    /**
     * アイコンとテキストを作成する
     * @param ctx 
     * @param txt 
     */
    private makeContext(ctx: CanvasRenderingContext2D, txt: string){
        //背景にアイコンを描いておく
        ctx.drawImage(this.iconImage, 0, 0, 35, 35);
        //テキストの色
        ctx.fillStyle = '#FFFF00';
        //テキストのサイズ
        ctx.font = 'bold 40px sans-serif';
        //桁数によって表示位置調整
        //TODO: 2桁しか対応していない
        var xp = txt.length == 1 ? 10 : 0;
        //テキストの描画
        ctx.fillText(txt, xp, 30, 35);
    }

    /**
     * アイコンと数字をHTMLに反映させる
     * @param canvas 
     */
    private changeFaviText(canvas: HTMLCanvasElement){
        var link: HTMLLinkElement = document.createElement('link');
        link.type = 'image/x-icon';
        link.rel  = 'shortcut icon';
        link.href = canvas.toDataURL("image/x-icon");

        //TODO: exclude jQuery
        $('link[rel="shortcut icon"]').remove();
        $('head').append(link);
    }
}



/**
 * window.onload event
 */
window.addEventListener("load", (e) => {
    var iconImage = new Image();
    iconImage.src = './static/favicon.png';
    var fav = new FaviconChanger(iconImage);

    var index = 99;
    $('#changeCounter').on('click', () => {
        fav.changeIcon(index);
        index--;
    });
});

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="shortcut icon" type="image/png" href="./static/favicon.png">
    <title>Document</title>
</head>
<body>
    <div id="contents">
    </div>
    <script src="./dist/index.js"></script>

    <button id="changeCounter" title="Change"> Change </button>

    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
</body>
</html>

以上!(いろいろ手抜き)

動作確認


デフォアイコンはお寿司屋さん


ぎゅーーっとアイコンだけにしてみる


2桁表示


1桁表示

ボタンを押すだけです
0は表示せずに初期状態になります

で、何が言いたかったのかというと
・タイマーの終わりごろ、ラスト60秒くらいからカウントダウンしたらどうか
・秒単位は焦るのでそれはラスト5秒かな(そこまでは10秒とか15秒単位とかいろいろ
・アイコンが変わるのつい見てしまうので、さりげなく動いている感を出したい
・文字じゃなくてアイコンがぐるぐる回転してもいいかもしれない
・サイトのアイデンティティよりも実用性(・・・実用性あるのかな

終わりに

(作者さんすいません)勝手にSushi Workから着想して、かつ応援するために検証したわけですが、
特定の用途ではわりと使えるかもしれません(動くブラウザが限定されるけど)

実運用するにはいろいろ足りてませんが、頑張ればいろいろできそう
というかアニメーションできるよね・・・・
35px四方に収めるムービーを誰か作ってくれるはず😇

要望があればまるっとコードUPします(需要なさそう)

Discussion

ログインするとコメントできます