FirefoxのアドオンでSVG画像を「色を変えられるアイコン」として使う方法

この記事は[所属会社のブログとのクロスポスト](http://www.clear-code.com/blog/2017/12/26.html)です。

# Photonのアイコン画像を劣化の無いSVG形式で入手する

Firefox 57以降のバージョンで採用されているアイコンや配色などの視覚的デザインセットには「[Photon](https://design.firefox.com/photon/)」という名前が付いています。サイドバーやツールバーボタンのパネルなど、Firefox用のアドオンで何らかのGUIを提供する場合には、このPhotonと親和性の高い視覚的デザインにしておく事が望ましいです。

![（Photon Iconsのスクリーンショット）](https://qiita-image-store.s3.amazonaws.com/0/61387/e8620493-37e5-cecd-b89f-2837de5439dd.png)
Photonのデザイン指針に則って作成されたFirefoxの各種アイコンは、[SVG形式のデータが公開されています](https://design.firefox.com/icons/viewer/)。SVGのようなベクター画像形式は拡大縮小しても画質が劣化しないため、極端に解像度が高い環境でもレイアウトの崩れや強制的な拡大縮小によるボケなどの発生を気にせずに使えるのが魅力です。PhotonのアイコンセットはライセンスとしてMPL2.0が設定されているため、自作のアドオンにも比較的容易に組み込んで使えますので、是非活用していきたい所です。

# SVGアイコンの簡単な使い方

アドオンでSVGアイコンを使う時は、背景画像として使う方法が一番簡単です。ただし単純にある要素の背景画像に設定するのではなく、`::before`または`::after`疑似要素の背景画像として設定する方が、親要素の背景画像や枠線などと組み合わせられて何かと都合が良いのでお薦めです。

例えば何かのGUI要素に「閉じる」ボタンのようなUIを付けたい場合、そのGUI要素にあたる要素が`<span href="#" class="closebox"></span>`として定義されているのであれば、タブのクローズボックス等で使われているアイコン画像の`close-16.svg`を以下の要領でアイコン画像として表示できます。
![（::after疑似要素として表示されたSVGアイコン画像の例）](https://qiita-image-store.s3.amazonaws.com/0/61387/50d1948b-9b47-b502-cd71-c37a7afdfbf2.png)

```css
/* 対象の要素そのものではなく、::beforeや::after疑似要素にスタイル指定を行うことで、
   アイコン画像の<img>要素を挿入したように扱うことができる。 */
.closebox::after {
  /* 疑似要素を有効化するために、空文字を内容として指定する。
     （contentが無指定だと::before/::after疑似要素は表示されない。） */
  content: "";
  /* <img>と同様に、幅と高さを持つボックス状のインライン要素として取り扱う。 */
  display: inline-block;
  /* 表示したいアイコン画像の大きさを、この疑似要素自体の大きさとして設定する。 */
  min-height: 24px;
  min-width: 24px;
  /* SVG画像をボックスの大きさぴったりに拡大縮小して背景画像として表示する。背景色は透明にする。 */
  background: transparent url("./close-16.svg") no-repeat center / 100%;
}
```

# SVGアイコンの色を変えるには？

[Photonのアイコン画像はいずれも黒または白一色のべた塗りで、意味はシルエット（形）で表すようにデザインされています](https://firefoxux.github.io/photon/visuals/iconography.html)。これには、カラフルなアイコンだとテーマの色に合わなくなる事があるのに対し、シルエットのみのアイコンであればテーマに合わせた色に変えて使いやすいからという理由があります。

実は、背景画像として表示されるSVG画像の色は、FirefoxにおいてはCSSでの指定だけで変える事ができます。SVG画像の中で`<path fill="context-fill">`のように`fill="context-fill"`が設定されている閉領域の塗り潰し色は、以下のようにするとCSSの側での指定を反映させられます。
![（同じSVG画像を使用して、塗り潰しの色をCSSの指定で変えた状態）](https://qiita-image-store.s3.amazonaws.com/0/61387/7661a271-e759-9c61-599a-67ecdf77d55b.png)


```css
.closebox::after {
  content: "";
  display: inline-block;
  min-height: 24px;
  min-width: 24px;
  background: transparent url("./close-16.svg") no-repeat center / 100%;

  /* 塗りの色を指定 */
  fill: #EFEFEE;
  /* CSSのfillプロパティの値をSVG画像のcontext-fillに反映するための指定 */
  -moz-context-properties: fill;
}
```

ここではカラーコードを直接指定していますが、以下のように[カスタムプロパティ（CSS変数）](https://developer.mozilla.org/en-US/docs/Web/CSS/--*)を使えば色の指定だけを簡単に差し替えられます。

```css
:root {
  /* 冒頭、最上位の要素で色だけを定義 */
  --background-base-color: #EFEFFF;
  --foreground-base-color: #0D0D0C
}

...

.closebox::after {
  ...
  /* 後の箇所では定義済みの色を名前で参照する */
  fill: var(--foreground-base-color);
  ...
}
```

これをうまく使えば、[ツリー型タブ](https://addons.mozilla.org/firefox/addon/tree-style-tab/)のように複数テーマを切り替えたり[`themes.onUpdated`](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/theme/onUpdated)を監視して他のアドオンが設定したテーマの色を自動的に反映したりといった事も容易に実現できます。

ただし、ここで1つ残念なお知らせがあります。実は、*上記の指定はFirefoxの既定の状態では機能しない*のです（Firefox 57現在）。

上記のような指定はFirefox自体のGUIの外観を定義するのにも使われているのですが、ここで`fill`と共に使われている`-moz-context-properties`が曲者です。このプロパティは[今のところFirefoxの独自拡張プロパティで、`about:config`で`properties.content.enabled`を`true`に変更しない限り、アドオンが提供するサイドバーやツールバーのポップアップなどの中では使えない](https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-context-properties)ようになっているため、結局はSVGのアイコン画像は黒一色で表示されるという結果になってしまうのです。[Bug 1388193](https://bugzilla.mozilla.org/show_bug.cgi?id=1388193)または[Bug 1421329](https://bugzilla.mozilla.org/show_bug.cgi?id=1421329)が解消されるまでは、この方法は一般的なユーザーの環境では使えないという事になります。


# maskを使った「CSSの指定だけでSVGアイコンの色を変える」代替手法

でも諦めるのはまだ早いです。Photonのアイコンセットのようにシルエットだけで構成されたSVG画像であれば、[`mask`関連の機能](https://developer.mozilla.org/en-US/docs/Web/CSS/mask)で上記の例と同等の事ができます。具体的には以下の要領です。

```css
.closebox::after {
  content: "";
  display: inline-block;
  min-height: 24px;
  min-width: 24px;
  /* SVG画像は背景画像としては使わない。 */
  /* background: transparent url("./close-16.svg") no-repeat center / 100%; */

  /* まず、アイコンの色として使いたい色で背景を塗り潰す */
  background: #EFEFEE;
  /* 次に、SVG画像をボックスの大きさぴったりに拡大縮小してマスク画像として反映する */
  mask: url("./close-16.svg") no-repeat center / 100%;
}
```

疑似要素自体は指定の背景色の矩形として描画されますが、その際マスク画像の形に切り抜かれるため、結果として「単色で、SVG画像のシルエットの形をしたアイコン」のように表示されるという仕組みです。

この代替手法には元の手法よりもCPU負荷が高くなるというデメリットがあります。特に`:hover`等の疑似クラスやアニメーション効果と組み合わせる時には、CPU負荷が一時的に100％に張り付くようになる場合もあり得ます。モバイルPCの電池の持ちが悪くなるなどの副作用が生じる事になりますので、使用は注意深く行ってください。

[Bug 1388193](https://bugzilla.mozilla.org/show_bug.cgi?id=1388193)または[Bug 1421329](https://bugzilla.mozilla.org/show_bug.cgi?id=1421329)のどちらかが解消された後は、この代替手法を速やかに削除できるように、最上位の要素のクラスなどを見て反映するスタイル指定を切り替えるのがお薦めです。以下はその指定例です。

```css
.closebox::after {
  content: "";
  display: inline-block;
  min-height: 16px;
  min-width: 16px;
  /* 将来的に反映したい指定 */
  background: url("./close-16.svg") no-repeat center / 100%;
  fill: #EFEFFF;
  -moz-context-properties: fill;
}

:root.simulate-svg-context-fill .closebox::after {
  /* 後方互換性のための代替手法の指定 */
  background: #EFEFFF;
  mask: url("./close-16.svg") no-repeat center / 100%;
}
```


# まとめ

FirefoxのアドオンでSVG画像をアイコンとして使う場合の小技をご紹介しました。

GUIを持つアドオンを作る場合、ユーザーを迷わせないで済むように、アイコン画像はなるべくFirefox本体の物とデザインを揃えておいた方が良いです。[Photonのアイコンセット](https://design.firefox.com/icons/viewer/)を使い、皆さんも洗練されたデザインのGUIを実装しましょう。