やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

アイコンの設計を考える

アイコンの設計を考える

 レスポンシブなアイコンを作りたい。色やサイズを適切に変更でき、画像データは単一でメンテが容易。そんな理想のアイコンを、どうすれば実現できるかを考える。

テーマの必要性

 WEBサイトなどのコンテンツを作るとき、背景色などの組合せであるテーマを決める必要がある。このうち代表的なテーマは次の二つ。

テーマ 概要
ライトモード 背景=白/文字=黒
ダークモード 背景=黒/文字=白

 ふつうサイトはライトモードだけであることが多い。これは紙の白と似ているため受け入れやすいからだと思われる。

 だが、夜に閲覧すると問題がある。白い光を浴びると目が痛いし、ブルーライトが含まれており眠れなくなる。夜でも負担なく閲覧したい。そこでダークモードが欲しいわけだ。

アイコンの必要性

  • 視認性を高める
  • 識別性を高める
  • 表示領域を節約する

種別 内容
アイコン
テキスト モナコイン

 テキストよりもアイコンのほうが簡潔明瞭。テキストの中にアイコンが含まれている時は、より視認性が高まる。

アイコンの要件

  • アス比1:1
  • サイズ可変(16, 32, 48, 64, ...)
  • 色可変(黒, 白, ...)
  • 単一ファイル(保守性を高めるため)
  • JSで色やサイズを変更したい

 これが実現できるのはSVG形式のみ。

 もしPNGなどのラスタ形式だと、色やサイズを可変にできず、その数だけファイル数が増えて管理が大変になる。仮にサイズが4種、色が2種なら8種の画像ファイルを作成することになる。すると容量はその分だけ増えてしまう。あまりに非効率的である。

 よってアイコンはSVG一択。

暗号通貨アイコン

 アンコンの一例として以下サイトから拾ってくる。

 たとえばモナコイン(MONA)を見てみる。画像ファイルは次の4種ある。

種類画像長所短所
icon グラデーション等あり高品質大容量
colorカラフルで楽しいテーマと同化し見えなくなる可能性あり
black軽量&ライトモードで視認性を保障単色+透明で退屈
white軽量&ダークモードで視認性を保障単色+透明で退屈

 それぞれ長所と短所がある。もっとも可用性の高いアイコンはblackwhite。これはライト/ダーク各モードに対応できるから。

ファイル統合

 SVGファイルは一つにしたい。blackwhiteSVGのパスが同じで、色だけが違う。そこで色の部分を可変にできるよう、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>である。

  1. 画像シンボルを定義する
  2. 画像シンボルを使う(色・サイズを指定する)

 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>

MONA

 ルビの位置を下にしてみる。

<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>

MONA

フォントサイズと同じ大きさにしたい

<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>

 SVGwidth,heightCSSに移譲すると以下。

<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>

 長い。たかがアイコンを表示するのにこれは長すぎる。

MONA

もっと短く書きたい

<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ごとに違う値なら自動化できず、個別に対応せねばならない。

 アイコン化したいSVGviewBoxを全て統一するなど事実上不可能。製作者が違えば仕様も違う。マジで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を使いたい場合、クソみたいに大変な工程となる。

  1. SVGの画像を探す
  2. viewBox,width,heightを把握する
  3. 表示したいサイズになるよう各値を計算してセットする
  4. 上記を全SVGに対して行う

タグが冗長

 長すぎて書いてられない。HTMLもSVGも書きたくない。これを解決するにはテンプレートエンジン、Markdownパーサ、MDXの環境、fontawesome風コード、VanJSで動的HTML生成などして書くしかない。めちゃ大変そう。

 アイコンはSVG画像よりJSライブラリにしてしまったほうがいいかもしれない。それを作るのが大変だが。