アイコンの設計を考える
アイコンの設計を考える
レスポンシブなアイコンを作りたい。色やサイズを適切に変更でき、画像データは単一でメンテが容易。そんな理想のアイコンを、どうすれば実現できるかを考える。
テーマの必要性
WEBサイトなどのコンテンツを作るとき、背景色などの組合せであるテーマを決める必要がある。このうち代表的なテーマは次の二つ。
テーマ | 概要 |
---|---|
ライトモード | 背景=白/文字=黒 |
ダークモード | 背景=黒/文字=白 |
ふつうサイトはライトモードだけであることが多い。これは紙の白と似ているため受け入れやすいからだと思われる。
だが、夜に閲覧すると問題がある。白い光を浴びると目が痛いし、ブルーライトが含まれており眠れなくなる。夜でも負担なく閲覧したい。そこでダークモードが欲しいわけだ。
アイコンの必要性
- 視認性を高める
- 識別性を高める
- 表示領域を節約する
種別 | 内容 |
---|---|
アイコン | |
テキスト | モナコイン |
テキストよりもアイコンのほうが簡潔明瞭。テキストの中にアイコンが含まれている時は、より視認性が高まる。
アイコンの要件
- アス比1:1
- サイズ可変(16, 32, 48, 64, ...)
- 色可変(黒, 白, ...)
- 単一ファイル(保守性を高めるため)
- JSで色やサイズを変更したい
これが実現できるのはSVG形式のみ。
もしPNGなどのラスタ形式だと、色やサイズを可変にできず、その数だけファイル数が増えて管理が大変になる。仮にサイズが4種、色が2種なら8種の画像ファイルを作成することになる。すると容量はその分だけ増えてしまう。あまりに非効率的である。
よってアイコンはSVG一択。
暗号通貨アイコン
アンコンの一例として以下サイトから拾ってくる。
たとえばモナコイン(MONA
)を見てみる。画像ファイルは次の4種ある。
種類 | 画像 | 長所 | 短所 |
---|---|---|---|
icon | グラデーション等あり高品質 | 大容量 | |
color | カラフルで楽しい | テーマと同化し見えなくなる可能性あり | |
black | 軽量&ライトモードで視認性を保障 | 単色+透明で退屈 | |
white | 軽量&ダークモードで視認性を保障 | 単色+透明で退屈 |
それぞれ長所と短所がある。もっとも可用性の高いアイコンはblack
とwhite
。これはライト/ダーク各モードに対応できるから。
ファイル統合
SVGファイルは一つにしたい。black
とwhite
はSVGのパスが同じで、色だけが違う。そこで色の部分を可変にできるよう、SVGコードを修正する。
まずはwhite
のファイルをテキストエディタで開く。そして塗りつぶし色であるfill
の値をwhite
からcurrentColor
※に変える。これはCSSプロパティcolor
の値を示す。ようするに文字色である。
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path fill="currentColor" d="M16 32C7.163 32 0 24.837 0 16S7.163 0 16 0s16 7.163 16 16-7.163 16-16 16zm7.53-18.586L22.105 7l-2.797 4.414a14.096 14.096 0 00-6.617 0L9.902 7l-1.43 6.414C6.937 14.642 6 16.247 6 18.009c0 3.86 4.476 6.989 9.997 6.989s9.997-3.13 9.997-6.989c-.001-1.762-.93-3.367-2.465-4.595zM10.442 16.35h-.666l1.627-1.876h1.184l-2.145 1.876zm5.504 4.584l-2.766-4.872.683-.39.617 1.085h3.021l.644-1.09.676.402-2.875 4.865zm5.613-4.584l-2.146-1.876h1.192l1.625 1.876h-.671zm-5.6 3.015l1.075-1.82h-2.108l1.033 1.82z"/></svg>
シンボル化
- 画像を複数ヶ所で使いたい
- 色やサイズをもっと自由に変えたい
これを叶えるのがSVGの<defs>
,<symbol>
,<use>
である。
- 画像シンボルを定義する
- 画像シンボルを使う(色・サイズを指定する)
SVGでは次のように画像をシンボルとして定義する。
<svg> <defs> <symbol id="識別子"> <path d="..."> </symbol> </defs> </svg>
<svg><use xlink:href="#識別子" fill="currentColor"></svg> <svg><use xlink:href="#識別子" fill="red"></svg> <svg><use xlink:href="#識別子" fill="green" width="64" height="64"></svg>
<symbol>
で型を作り、それを<use>
で使う。このとき色やサイズなどを指定すると反映される。
<svg display="none" xmlns="http://www.w3.org/2000/svg"><defs> <symbol id="svg-icon-mono-coin-mona" width="32" height="32" viewBox="0 0 32 32"><path d="M16 32C7.163 32 0 24.837 0 16S7.163 0 16 0s16 7.163 16 16-7.163 16-16 16zm7.53-18.586L22.105 7l-2.797 4.414a14.096 14.096 0 00-6.617 0L9.902 7l-1.43 6.414C6.937 14.642 6 16.247 6 18.009c0 3.86 4.476 6.989 9.997 6.989s9.997-3.13 9.997-6.989c-.001-1.762-.93-3.367-2.465-4.595zM10.442 16.35h-.666l1.627-1.876h1.184l-2.145 1.876zm5.504 4.584l-2.766-4.872.683-.39.617 1.085h3.021l.644-1.09.676.402-2.875 4.865zm5.613-4.584l-2.146-1.876h1.192l1.625 1.876h-.671zm-5.6 3.015l1.075-1.82h-2.108l1.033 1.82z"/></symbol></defs></svg>
<svg width="32" height="32" viewBox="0 0 32 32"><use xlink:href="#svg-icon-mono-coin-mona"></use></svg> <svg width="32" height="32" viewBox="0 0 32 32"><use xlink:href="#svg-icon-mono-coin-mona" fill="currentColor"></use></svg> <svg width="16" height="16" viewBox="0 0 32 32"><use xlink:href="#svg-icon-mono-coin-mona" fill="red"></use></svg> <svg width="64" height="64" viewBox="0 0 32 32"><use xlink:href="#svg-icon-mono-coin-mona" fill="green"></use></svg>
ポイントは<symbol>
定義している<path>
のfill
属性をなくし、<use>
時に指定している所だ。もし<path>
で指定すると固定化されてしまうので注意。
面倒なのはwidth
, height
, viewBox
の指定。<use>
するとき毎回<svg>
に指定しないと異常に大きくなってしまう。表示サイズを指定するときはwidth
, height
の値を指定すべきで、viewBox
の値は変更しないこと。さもなくば画像の一部だけが表示されるなど不具合が起きる。(このあたり仕様が複雑で面倒。コードも冗長になり残念)
ruby化
アイコン画像だけでなく補足テキストも表示したいことがある。
<ruby><svg width="16" height="16" viewBox="0 0 32 32"><use xlink:href="#svg-icon-mono-coin-mona" ></use></svg><rt>MONA</rt></ruby>
ルビの位置を下にしてみる。
<ruby style="ruby-position:under;"><svg width="16" height="16" viewBox="0 0 32 32"><use xlink:href="#svg-icon-mono-coin-mona" ></use></svg><rt>MONA</rt></ruby>
フォントサイズと同じ大きさにしたい
<style> :root {--font-size:24px;} body {font-size: var(--font-size);} </style> <p>モナコイン<ruby style="ruby-position:under;"><svg style="width:var(--font-size);height:var(--font-size);" viewBox="0 0 32 32"><use xlink:href="#svg-icon-mono-coin-mona" ></use></svg><rt>MONA</rt></ruby>です。</p>
CSSカスタムプロパティを使う。最初にフォントサイズを定義しておき、それを<svg>
のstyle
属性値で参照する。
残念ながら以下のようにwidth
, height
属性値で参照しようとしても反映されなかった。
<p>モナコイン<ruby style="ruby-position:under;"><svg width="var(--font-size)" height="var(--font-size)" viewBox="0 0 32 32"><use xlink:href="#svg-icon-mono-coin-mona" ></use></svg><rt>MONA</rt></ruby>です。width,heightだとvar()反映されず……</p>
UIで可変にする
<style> :root { --font-size: 24px; } body { font-size: var(--font-size); } svg.glyph { width: var(--font-size); height: var(--font-size); /*viewBox: 0 0 32 32;*/ /*view-box: 0 0 32 32;*/ } </style> <script> window.addEventListener('DOMContentLoaded', (event) => { const slider = document.querySelector('#font-size-slider') slider.addEventListener('input', (e) => { console.log(e.target.value) document.querySelector(':root').style.setProperty('--font-size', `${e.target.value}px`) document.querySelector(`#font-size-value`).textContent = `${e.target.value}px` }) slider.focus() }); </script> <input id="font-size-slider" type="range" min="16" max="64" value="24"><span id="font-size-value"></span> <svg display="none" xmlns="http://www.w3.org/2000/svg"><defs> <symbol id="svg-icon-mono-coin-mona" width="32" height="32" viewBox="0 0 32 32"><path d="M16 32C7.163 32 0 24.837 0 16S7.163 0 16 0s16 7.163 16 16-7.163 16-16 16zm7.53-18.586L22.105 7l-2.797 4.414a14.096 14.096 0 00-6.617 0L9.902 7l-1.43 6.414C6.937 14.642 6 16.247 6 18.009c0 3.86 4.476 6.989 9.997 6.989s9.997-3.13 9.997-6.989c-.001-1.762-.93-3.367-2.465-4.595zM10.442 16.35h-.666l1.627-1.876h1.184l-2.145 1.876zm5.504 4.584l-2.766-4.872.683-.39.617 1.085h3.021l.644-1.09.676.402-2.875 4.865zm5.613-4.584l-2.146-1.876h1.192l1.625 1.876h-.671zm-5.6 3.015l1.075-1.82h-2.108l1.033 1.82z"/></symbol></defs></svg> <p>モナコイン<ruby style="ruby-position:under;"><svg class="glyph" viewBox="0 0 32 32"><use xlink:href="#svg-icon-mono-coin-mona" ></use></svg><rt>MONA</rt></ruby>です。</p>
viewBox
の指定をCSSにまとめたかったが、できなかった。冗長であることは避けられない。
See the Pen resize-svg by ytyaru (@ytyaru) on CodePen.
リンクを付与する
<style> :root {--font-size:24px;} body {font-size: var(--font-size);} </style> <a href="https://ja.wikipedia.org/wiki/Monacoin" target="_blank" rel="noopener noreferrer"><ruby style="ruby-position:under;"><svg style="width:var(--font-size);height:var(--font-size);" viewBox="0 0 32 32"><use xlink:href="#svg-icon-mono-coin-mona"></use></svg><rt>MONA</rt></ruby></a>
<style> :root{--font-size:24px;} body{font-size:var(--font-size);} svg.glyph{width:var(--font-size);height:var(--font-size);} </style> <a href="https://ja.wikipedia.org/wiki/Monacoin" target="_blank" rel="noopener noreferrer"><ruby style="ruby-position:under;"><svg class="glyph" viewBox="0 0 32 32"><use xlink:href="#svg-icon-mono-coin-mona" ></use></svg><rt>MONA</rt></ruby></a>
長い。たかがアイコンを表示するのにこれは長すぎる。
もっと短く書きたい
<p>モナコイン<icon-mona>です。</p>
こんな感じで書きたい。
最初に思いつくのはWebComponentだ。でもこれ、終了タグを省略できないし、タグ名にハイフンを入れねばならない。しかもJavaScriptで実装せねばならない。
WebComponentは制限が多い上に、せっかく作っても冗長なタグになって短く書けない。以下のようになる。
<p>モナコイン<icon-mona></icon-mona>です。</p>
これでも先述に比べると十分短い。でも長いし読みにくい。
もっと楽したい
思えばHTML自体が冗長。
どうせならMarkdownで書きたい。以下のように。
# モナコイン
これはモナコイン{{icon-coin-mona}}です。
テンプレートエンジンやらMarkdownパーサやらで対応することになる。{{icon-coin-mona}}
の部分は先述のHTMLコードに置き換えるよう実装する。
MDXもある。先述のHTML要素を返すicon()
関数を用意しておき、以下のように書く。これでテンプレートエンジンやパーサは不要になる。
# モナコイン
これはモナコイン{icon('coin-mona')}です。
function icon(id) { return 先述のHTML要素 }
どちらにせよ環境構築が必要で大変そう。
IcoMoon][]
SVG画像からフォントファイルを作成できるらしい。いわゆる外字。
FontAwesome
FontAwesomeというアイコンをHTML要素で表示できるJSライブラリがある。
<i class="fa-brands fa-github"></i>
だいぶ短く書ける。色やサイズもその文脈に自動で合わせてくれる。以下でそれを確認した。
<style> :root { --fg-color: black; --bg-color: white; } body,select,input,textarea { color: var(--fg-color); background-color: var(--bg-color); } </style> <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/js/all.min.js"></script> <script> window.addEventListener('DOMContentLoaded', (event) => { const select = document.querySelector('#color-scheme') select.addEventListener('change', (e) => { document.querySelector(':root').style.setProperty('--fg-color', (('dark'===e.target.value) ? 'white' : 'black')) document.querySelector(':root').style.setProperty('--bg-color', (('dark'===e.target.value) ? 'black' : 'white')) }) select.focus() }); </script> <h1>FontAwesome<i class="fa-brands fa-github"></i></h1> <p> FontAwesomeを使ってGithubのアイコン<i class="fa-brands fa-github"></i>を表示する。</p> <select id="color-scheme"><option value="light">light</option><option value="dark">dark</option></select>
どうやって<i>
タグでSVG画像を表示させているのか。ライブラリの中身を見てみないと分からない。でもこれを真似すれば、いい感じに短く、読みやすくできそう。でも実装、大変そう。
See the Pen fontawesome-svg by ytyaru (@ytyaru) on CodePen.
VanJSで書く
<style>*{padding:0;margin:0;}</style> <script src="https://vanjs.org/code/van-1.5.0.nomodule.min.js"></script> <script> window.addEventListener('DOMContentLoaded', (event) => { const color = { fg: van.state('black'), bg: van.state('white'), } const size = van.state(256) const select = van.tags.select({id:`color-scheme`, style:()=>`color:${color.fg.val};background-color:${color.bg.val};`, onchange:(e)=>{ color.fg.val = (('dark'===e.target.value) ? 'white' : 'black') color.bg.val = (('dark'===e.target.value) ? 'black' : 'white') }}, ['light','dark'].map(v=>van.tags.option({value:v}, v))) const {svg, defs, symbol, use, path} = van.tags('http://www.w3.org/2000/svg') const icon = svg({xmlns:``, width:()=>size.val, height:()=>size.val, viewBox:()=>`0 0 32 32`}, path({fill:()=>color.fg.val, d:`M16 32C7.163 32 0 24.837 0 16S7.163 0 16 0s16 7.163 16 16-7.163 16-16 16zm7.53-18.586L22.105 7l-2.797 4.414a14.096 14.096 0 00-6.617 0L9.902 7l-1.43 6.414C6.937 14.642 6 16.247 6 18.009c0 3.86 4.476 6.989 9.997 6.989s9.997-3.13 9.997-6.989c-.001-1.762-.93-3.367-2.465-4.595zM10.442 16.35h-.666l1.627-1.876h1.184l-2.145 1.876zm5.504 4.584l-2.766-4.872.683-.39.617 1.085h3.021l.644-1.09.676.402-2.875 4.865zm5.613-4.584l-2.146-1.876h1.192l1.625 1.876h-.671zm-5.6 3.015l1.075-1.82h-2.108l1.033 1.82z`}) ) van.add(document.body, van.tags.main({style:()=>`width:100%;height:100%;color:${color.fg.val};background-color:${color.bg.val};`}, van.tags.h1('ライト/ダークモード切替'), select, icon)) select.focus() }); </script>
めちゃ大変そう。でも一番自由に書けそう。FontAwesomeのコード解析と合わせてやれば最高か……いや大変すぎて死ぬ。
See the Pen van-svg by ytyaru (@ytyaru) on CodePen.
アイコンの使用における問題点
viewBox
モジュール化すべくSVGを外部ファイルに書き出したい。でも<use>
するときviewBox
に定義したのと同じ値を書かねばならない。もしSVGごとに違う値なら自動化できず、個別に対応せねばならない。
アイコン化したいSVGのviewBox
を全て統一するなど事実上不可能。製作者が違えば仕様も違う。マジでviewBox
のクソ仕様なんとかして。
定義
<svg display="none" xmlns="http://www.w3.org/2000/svg"><defs> <symbol id="svg-icon-mono-coin-mona" width="32" height="32" viewBox="0 0 32 32"><path d="..."/></symbol></defs></svg>
使用
<svg width="32" height="32" viewBox="0 0 32 32"><use xlink:href="#svg-icon-mono-coin-mona" fill="currentColor"></use></svg>
私としては以下のように使いたかった。
<svg><use xlink:href="#svg-icon-mono-coin-mona" fill="currentColor"></use></svg>
<svg width="16" height="16"><use xlink:href="#svg-icon-mono-coin-mona" fill="currentColor"></use></svg>
現状、SVGを使いたい場合、クソみたいに大変な工程となる。
タグが冗長
長すぎて書いてられない。HTMLもSVGも書きたくない。これを解決するにはテンプレートエンジン、Markdownパーサ、MDXの環境、fontawesome風コード、VanJSで動的HTML生成などして書くしかない。めちゃ大変そう。
アイコンはSVG画像よりJSライブラリにしてしまったほうがいいかもしれない。それを作るのが大変だが。