% cat

SVG アイコンを CSS 変数とか併せて良さげに使う

こんにちは。@rigani_c です。
みなさん、SVG 吸ってますか? SVG 吐いてますか?
Web ページ上で自前の SVG アイコンを使用するときの良さげな手法を持ってきました。

結論からいうと
SVG スプライトを use タグ使って id 指定で呼んで CSS 変数でスタイルを変更する。更に currentColor 使うと WebFont っぽくなって最高
です。

環境は macOS Mojave の Chrome 70 です。
それではご査収ください。

本記事で SVG アイコンに求めたいこと

以下の通りです。

  • Web サイト内共通の CSS でパーツごとに色を変えられる
  • ページの表示を阻害しない
  • 修正や管理が楽にできる(@rigani_c 的に)

これだけ満たしていれば御の字ですね!

そもそも SVG ってそんなにええのん?

めっちゃいいです。
PNG で 50KB とかの画像が SVG で 500B になったりします。
(大した差ないじゃんって方はインフルエンザの塩基配列の情報量について調べて、大した差にしてください)
更に SVG(Scalable Vector Graphics)は名前の通り、どんなサイズにもキレイにスケールできるので最強です。
アイコンはみんな SVG でいいと思います。
あ、このブログのロゴ SVG じゃないじゃん。やば

SVG を Web ページに表示する方法

色々あります。思いつくものをざっと並べると

  • img タグで読み込む
  • CSS の background-image などで読み込む
  • インラインで直接書く
  • canvas タグに描画
  • object, iframe タグなどで読み込む

でしょうか。他にもありそう。
上記はいずれも dataURI で取り扱えるはずです。
また、SVG スプライトを利用するときは icons.svg#nyancat というように ID 付きで呼び出すと、取り回しが楽になりますね。


 * * *

SVG を WebFont 化して使用する手もあります。
私は WebFont 自体好きなのですが、SVG からの変換だと管理がだるいのと、単色しか表現できない点で見送りました。

初見狩り・SVG の参照/処理モード

SVG はベクター画像、つまり図形なので、色を変えたりアニメーションするといったことが出来ます。
Web ページ上でそれらの操作をするとしたら CSS ですよね。

それではさっそく。動作を確認してみましょう。
SVG 内の style タグで :hover で fill が red になるようにして、

要素の ::before の background-image に SVG を指定して、マウスホバー...

あれ、変わらないですね。

それなら img タグで SVG を呼んで、マウスホバー...

変わらないじゃん!!ブラウザこわれた!!

いいえ、ブラウザは壊れていません。
SVG は読み込み方によって参照モード・処理モードが変化し、機能が制限されたりするのです。
SVG を img タグや background-image で読み込む時は :hover などのインタラクションは無効となります。SVG 内で設定された animation など、インタラクション不要なものは動きます。
インタラクションを生きたままにするには、処理モードが「動的インタラクティブモード(Dynamic interactive mode)」である必要があります。

では、どう読み込めば動的インタラクティブモードになるのでしょうか?
方法は大まかに以下の三種です。

  • そのまま SVG ファイルをブラウザで開く🌏
  • object タグや iframe タグで読み込む📄
  • インラインで直接 SVG を書く🖋

 * * *

SVG 参照モード・処理モードに関して、私が参考にした記事です。正確な情報はこちらから。

SVG Integration
https://svgwg.org/specs/integration/

埋め込みドキュメントの壁

「object タグや iframe タグで読み込む」を採った場合、マウスイベントなどを SVG に伝えることには成功します。
しかし、この場合 SVG にスタイルを効かせるには、埋め込まれたドキュメント内にスタイルを配置する必要があります。
JavaScript を使用して style タグを挿入するのも、SVG 内で CSS を読み込むのも、アイコンのためにしては大げさすぎますね。

インライン SVG

「インラインで直接 SVG を書く」を採った場合、普通の HTML の要素を扱うのと同等の操作が可能となります。これはひとつの解であるかのように思えます。
しかし、Web サイト上で頻繁に使われるであろうアイコン群を(必要な分だけではありますが)ページのロード毎に配信しなければならないというデメリットがあります。
HTML のレスポンスサイズのほとんどがアイコン、なんてことも...

Shadow DOM の壁

インライン SVG のデメリットを回避すべく、use タグの使用を検討してみます。
use タグは SVG 内のノードを参照し、use タグ下に呼び出すことができます。

使い方はこちらの記事がわかりやすいかと思います。

SVGをuseタグで使いまわす
https://qiita.com/mzzzk/items/8bb2623d78c8444bbbef

しかし、ここにも問題が。
use タグで呼び出されたノードは shadow-root (closed) 配下となってしまい、外部からの操作を遮断してしまいます。これは Shadow DOM です。(ということは、Shadow DOM 非対応だと use タグは動作しない?)
CSS は外部から Shadow DOM 内部のスタイル変更を行うことが "基本的には" できません。いわゆる Scoped CSS であり、これこそが Shadow DOM の利点ではあるのですが...

Shadow DOM の open/closed の違いについてはこちらの記事が理解に役立ちます。

Shadow DOM v1: 自己完結型ウェブ コンポーネント
https://developers.google.com/web/fundamentals/web-components/shadowdom?hl=ja#closed

更に、use でノードを呼び出すと、ノードに内包していた style タグなどは消えてしまいます。(消えない方法があれば教えてください!)

こうなると、呼び出されたノード側の CSS 継承や currentColor を利用するしかないように思えます。色を複数使いたいときはパーツごとに use で呼び出して、ひとつひとつ継承を利用して。

悲しくなってきました。もっと良い方法はないのでしょうか。

ありがとう CSS 変数

Shadow DOM の壁を超えてスタイルを充てる方法を模索してみます。

  • ::shadow
  • /deep/ (別名 ">>>")
  • CSS 変数

::shadow と /deep/ は Shadow DOM にスタイルを充てることのできる存在(になる予定)でしたが、きえてしまいました。おきのどくです。

Webapps/WebComponentsApril2015Meeting
https://www.w3.org/wiki/Webapps/WebComponentsApril2015Meeting

CSS 変数も CSS 継承以外の方法で Shadow DOM の壁を超えることができる存在です。
IE 以外のメジャーなブラウザであれば既に利用可能になっています。

MDN web docs: var()
https://developer.mozilla.org/ja/docs/Web/CSS/var

var() の第二引数に、変数が無効(設定されていないなど)の場合のデフォルト値を指定しておけるので、fill="var(--deep-color, currentColor)" などとしておけば、さながら WebFont のような働きをしてくれるでしょう。

<!-- icons.svg -->
<svg xmlns="http://www.w3.org/2000/svg">
  <defs>
    <symbol id="jewel" viewBox="0,0,10,8">
      <path fill="var(--light-color, currentColor)" d="M0,2l2-2h6l2,2L5,8"></path>
      <path fill="var(--deep-color, currentColor)" d="M7,2H3l2-2l2,2h3L5,8L0,2h3l2,6"></path>
    </symbol>
  </defs>
</svg>

CSS 変数のおかげで、私が SVG アイコンに求めることがほぼ実現されそうです。
たとえば、以下のように簡単に SVG を呼び出し、色の操作をすることができます。

<svg class="icon-jewel"><use href="icons.svg#jewel"></svg>
.icon-jewel {
  width: 1em;
  height: 1em;
  color: purple;
  &:hover { --deep-color: rebeccapurple; }
  use {
    transform-origin: center;
    transform: scale(.9);
  }
}

しかし、CSS 変数を用いた箇所の transition の効かせ方がわかりませんでした。情報お待ちしてます!

メインビジュアル用にスタイルを調整したものを置いておきます 📹

SVG スプライトの作成

あとは SVG スプライトの作成さえできれば、SVG アイコンの運用に憂いはありません。
が、ふとめんどくさくなってしまったので、本記事はここまで。SVG スプライトの作成は採用するフレームワークによって良いアプローチ変わりそうだし。

 * * *

弊社では Rails の採用が多く、Sprockets を使用しているため、assets:precompile 時に SVG スプライトを Fingerprint 付きで生成するのが良さそうです。
Svgeez で似たことできるのかな?
https://github.com/jgarber623/svgeez

Rails 6 で JavaScript のバンドラが Webpacker になるのに併せて、弊社では Sprockets 自体外してしまおうって話になりそう。

おわりの言葉

SVG アイコンなんてよく使いそうなのに、いろいろと壁が多かったです。ずっとインライン SVG でいいやって思ってました。これですっきり。
currentColor と CSS 変数のナイスムーブに感動しています。

記事中に誤った情報があれば @rigani_c にリプください。
ばいばい。

最新記事

BananaCI というツールを運用しています

こんにちは。[@endotakuya](https://github.com/endotakuya) です。 突然ですが皆さんは CI/CD 、してますか? オンプレミス型の Jenkins や Drone 、クラウド型の Cir...

endotakuya
2018年12月06日

シグナルと kill コマンドについてちゃんと調べてみた

開発中にプログラムが固まった時に `kill プロセスID` って打ったことがある方は多いかと思います 僕もそうで、 kill コマンドはプロセスを強制終了するためのコマンドだと思っていました ですが puma のログ...

tkhr0
2018年12月06日

Rails における弊社の CSS 設計

Hi. [rigani_c](https://twitter.com/rigani_c) です。 これはベーシックアドベントカレンダーの記事です。仲間たちの投稿一覧はこちら 🎄https://qiita.com/advent-...

rigani
2018年12月02日