意匠部

面白法人カヤック デザイナーブログ

チャレンジ石垣島制作秘話「ライトモード/ダークモードでファビコンを切り替える」編

こんにちは!
意匠部のおばらです。

今回はファビコンに関する記事です。
「チャレンジ石垣島」ウェブサイトではファビコンを OS やブラウザの外観モード(ライトモード/ダークモード)に応じて(一部の環境を除き)切り替えています。
今回はそのロジックをご紹介します。
(ロゴの制作秘話に関しては前回の記事を御覧ください)

目次

ファビコンを設定する

なにはともあれまずはファビコンを設定してみましょう。

<link rel="icon" href="favicon.png" />

PNG をファビコンにできるなんて。。良い時代になったものです。Adobe Photoshop にプラグインを入れて ICO ファイルを書き出していた頃が懐かしいです。

はい、これでおしまい!



と思いきや。。

ライトモード向けのファビコンがダークモードで見づらい

大変です!!ダークモードで見づらい!

最近の OS では外観モード(ライトモード/ダークモード)が選べてしまうのでした。ユーザによってファビコンの背景色が明るかったり暗かったり。。難儀な時代になったものです。とはいえ私も何を隠そうダークモード派。職業柄いつも酷使している目を少しでもいたわりたい。。文句は言えません。

というわけで外観モード(ライトモード/ダークモード)でファビコンを出し分けてみることにします。

メディアクエリでファビコンの見た目を切り替える

本記事でご紹介する方法はいずれもメディアクエリ(のメディア特性) prefers-color-scheme を利用します。

developer.mozilla.org

メディアクエリ prefers-color-scheme でユーザが選択した外観モード(ライトモード/ダークモード)に応じ処理を分岐できます。prefers-color-scheme に指定できる値は以下の 2 つです。

  • light
  • dark

注)no-preference は廃止されました

CSS でメディアクエリ prefers-color-scheme を用い処理を分岐するロジックは例えば以下です。

セレクタ {
  ライトモード時(or 特にモードが指定されていない場合)の記述
}

@media (prefers-color-scheme: dark) {
  セレクタ {
    ダークモードの時の記述
  }
}

メディアクエリ prefers-color-scheme をどこでどう使うかによってアプローチが異なります。本記事では私が試した 3 つのアプローチをご紹介します。

方法 ①「SVG 内に記述した CSS で切り替える」

SVG をファビコンにし、その SVG 内に<style>タグで CSS を記述する方法です。なお一部ブラウザは SVG のファビコンに対応していません。とはいえ SVG もファビコンにできるなんて。。良い時代になったものです。

対応状況は以下を御覧ください。

caniuse.com

SVG を作成する

任意のツールで SVG を作成します。今回は Adobe Illsutrator でロゴを作成しているため Illustrator を使用しました。

書き出す際 <svg> タグの widthheight 属性が省略されていると表示崩れを起こす環境があるため注意してください。気まぐれで WebGL で SVG をテクスチャに使ってみようと思い試したところ Safari で widthheight 属性が省略されている SVG を正しく表示できませんでした。他にも様々な環境・状況で表示崩れが発生します。

ちなみに Illustrator では SVG を書き出す際に「レスポンシブ(Responsive)」のチェックを入れると widthheight 属性が省略されます。

SVG をテキストエディタで開く

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
  <path d="M25.55821,28.8322a21.54656,..." />
  <path d="M16,1.88235A14.11561,..." />
  <path d="M29.30388,20.73146a14.12322,..." />
  <path d="M6.58824,8.47059c-.07681,..." style="fill:#26bfbf" />
  <path d="M24.91254,10.25761A29.04449,..." style="fill:#ff4d4d" />
  <path d="M18.20327,22.00412q.152.96771.37326,..." style="fill:#e0b550" />
</svg>

VS Code など任意のテキストエディタで SVG を開きます。SVG をプレビューするプラグインを入れておくと便利です。

<style> タグで CSS を追加する

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
  <style>
    .main {
      fill: #fff;
    }
    @media (prefers-color-scheme: dark) {
      .main {
        fill: #000;
      }
    }
  </style>
  <path class="main" d="M25.55821,28.8322a21.54656,..." />
  <path class="main" d="M16,1.88235A14.11561,..." />
  <path class="main" d="M29.30388,20.73146a14.12322,..." />
  <path d="M6.58824,8.47059c-.07681,..." style="fill:#26bfbf" />
  <path d="M24.91254,10.25761A29.04449,..." style="fill:#ff4d4d" />
  <path d="M18.20327,22.00412q.152.96771.37326,..." style="fill:#e0b550" />
</svg>

SVG 内に追加した<style> タグに、メディアクエリで条件分岐する CSS を記述しました。main というクラス名が付与された <path> タグの fill 属性の値を

  • ライトモードでは白 #fff
  • ダークモードでは黒 #000

としています。

SVG をファビコンに設定する

早速設定してみましょう。

<link rel="icon" type="image/svg+xml" href="favicon.svg" />

<link>タグにsizes="any"を指定している例も見られますが、2023年2月現在対応している環境がないため、sizes="any"は省いています。

これで(対応している環境では)ライトモード/ダークモードで色が切り替わります。

ブラウザごとの比較

環境依存があるため手元の環境で検証しました。

  • ページ読み込み時に切り替わるか?(リロードすると切り替わるか?)
  • ページ表示完了後も動的に切り替わるか?(OS の外観モードの変更と同期し、リロードしなくても動的に切り替わるか?)

注)Edge は Mac 用の Edge です

表 ①-1)SVG をファビコンにし、外観モード(ライトモード/ダークモード)を切り替えた際の挙動の比較
MacFirefoxv110.0ページ読み込み時に切り替わる
ページ表示完了後も動的に切り替わる
Chromev109.0.5414.87ページ読み込み時のみ切り替わる
Edgev110.0.1587.50
Safariv15.6.1SVG ファビコン非対応

参考までに<body> 内に <img> タグで配置した場合の挙動も併せて検証しました。

表 ①-2)SVG を <body> 内に <img> で配置し、外観モード(ライトモード/ダークモード)を切り替えた際の挙動の比較
MacChromev109.0.5414.87ページ読み込み時に切り替わる
ページ表示完了後も動的に切り替わる
Edgev110.0.1587.50
Firefoxv110.0
Safariv15.6.1いずれも切り替わらない

うーん。。 Mac だけを見てもブラウザによって挙動が異なります。。

<image> タグでの画像埋め込み

チャレンジのロゴはベクタデータのみで表現できるため CSS で見た目を簡単に変えることが簡単でした。 しかしベクタデータでは表現できない(ラスタデータでしか表現できない)デザインのファビコンもあります。 試しに <path> タグの代わりに <image> タグを用い SVG 内に画像を埋め込んでみましょう。

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
  <style>
    .light {
      display: block;
    }
    .dark {
      display: none;
    }
    @media (prefers-color-scheme: dark) {
      .light {
      display: none;
      }
      .dark {
      display: block;
      }
    }
  </style>
  <image class="light" width="32" height="32" href="data:image/png;..." />
  <image class="dark" width="32" height="32" href="data:image/png;..." />
</svg>

SVG を(<svg> タグを <body> 内にインラインで置くのではなく)<img> タグの src 属性や <link> タグの href 属性を経由して SVG を画像として読み込む場合、セキュリティ上の理由から <image> タグの href 属性で外部(別ファイル)の画像を読み込むことが出来ません。よってその制限を回避するため Data URI で埋め込みます。

developer.mozilla.org developer.mozilla.org

ブラウザごとの比較

表 ①-3)SVG をファビコンにし、外観モード(ライトモード/ダークモード)を切り替えた際の挙動の比較
MacFirefoxv110.0ページ読み込み時に切り替わる
ページ表示完了後も動的に切り替わる
Chromev109.0.5414.87ページ読み込み時のみ切り替わる
Edgev110.0.1587.50
Safariv15.6.1SVG ファビコン非対応
表 ①-4)SVG を <body> 内に <img> で配置し、外観モード(ライトモード/ダークモード)を切り替えた際の挙動の比較
MacChromev109.0.5414.87ページ読み込み時に切り替わる
ページ表示完了後も動的に切り替わる
Firefoxv110.0
Edgev110.0.1587.50
Safariv15.6.1いずれも切り替わらない

こちらもブラウザによって挙動が異なります。。

ファビコンを指定している<link>タグのmedia属性にメディアクエリを記述することも出来ます。

caniuse.com

developer.mozilla.org

<link rel="icon" href="favicon-light.png" media="(prefers-color-scheme: light)" />
<link rel="icon" href="favicon-dark.png" media="(prefers-color-scheme: dark)" />

この方法なら ファビコンは PNG でも SVG でも何でも良いですね! そして(SVG 内に記述した CSS メディアクエリでは動的に切り替わらなかった)Chrome と Edge でも動的に切り替わります。。やった!!

と思ったら今度は Firefox で切り替わりません。。

ブラウザごとの比較

表 ②-1)PNG をファビコンにし、外観モード(ライトモード/ダークモード)を切り替えた際の挙動の比較
MacChromev109.0.5414.87ページ読み込み時に切り替わる
ページ表示完了後も動的に切り替わる
Edgev110.0.1587.50
Firefoxv110.0そもそも切り替わらない
Safariv15.6.1そもそも切り替わらない

こちらも Mac だけを見てもブラウザによって挙動が異なります。。

方法 ③ 「JavaScript で切り替える」

JavaScript でライトモード/ダークモードを判別し、<link>タグの href 属性を書き換えてしまいましょう。

ビルトイン関数 matchMedia() を使用します。

caniuse.com

const link = document.querySelector('link[rel="icon"]'); // 何らかの方法で DOM 要素を取得
const mediaQueryList = matchMedia('(prefers-color-scheme:dark)');

const handleChange = () => {
  if (mediaQueryList.matches) {
    link.setAttribute('href', 'favicon-dark.png');
    console.log('ダークモードだよ!');
  } else {
    link.setAttribute('href', 'favicon-light.png');
    console.log('ダークモードじゃないよ!');
  }
};

mediaQueryList.addEventListener('change', handleChange);
handleChange();

注 1)古い実装では addEventListener ではなく addListener となる場合があるので注意してください。引数も異なります
注 2)setAttribute('href', '...') を通して設定するのではなく href 属性に直接代入してもよいかと思います。ただ取得の際は getAttribute('href') で取得する場合と href を直接参照する場合とで結果が異なるので注意してください。後者はパス解決後の絶対パスになります。昔 IE6 と IE7 で getAttribute('href') でも絶対パスになってしまうバグがあったような。。やっぱり良い時代になったものです。

developer.mozilla.org

ブラウザごとの比較

表 ③-1)JavaScript + matchMedia()で、外観モード(ライトモード/ダークモード)を切り替えた際の挙動の比較
MacChromev109.0.5414.87ページ読み込み時に切り替わる
ページ表示完了後も動的に切り替わる
Firefoxv110.0
Edgev110.0.1587.50
Safariv15.6.1いずれも切り替わらない
(JSでモードの変更は検知できるが href の書き換えが反映されない)

相変わらずの Safari 。。

しかし、この方法が現状一番広い環境に対応できそうです。 「チャレンジ石垣島」ウェブサイトでもこの方法を採用しています。

ちなみに、2023年2月現在 GitHub も同じ方法でファビコンを切り替えていますね!

<!-- ライトモード時 -->
<link rel="icon" class="js-site-favicon" type="image/svg+xml" href="https://github.githubassets.com/favicons/favicon.svg">
<!-- ダークモード時 -->
<link rel="icon" class="js-site-favicon" type="image/svg+xml" href="https://github.githubassets.com/favicons/favicon-dark.svg">

まとめ

長い戦いでした。。

OS やブラウザの外観モード(ライトモード/ダークモード)を判別し、ファビコンを切り替える方法をいくつかご紹介しました。しかし環境依存が激しいです。またライトモードだからファビコンの背景色が明るいとも限らないですし、ダークモードだからといって暗いとも限りません。

そしてブラウザにはプライベートモード(Incognito Mode)があります。 JavaScript で localStorageindexedDB などにアクセスしてみて検知するか。。とも考えましたが、そもそもプライベートモードで UI が暗くなるかどうかもブラウザ依存。。

ほんと難儀な時代になったものです。

参考文献

面白法人カヤック意匠部では
一緒に働く仲間を募集しています

アートディレクターデザイナーデザインエンジニアグラフィッカーイラストレーター他