LIFULLでフロントエンドエンジニアをしている齋藤です。
2008年入社なので15年目ぐらいの古株です。今は LIFULL HOME'S の賃貸部門でフロントエンド開発をしています。
いきなりですが、みなさんはアイコンをどう実装していますか?
アイコンフォント、スプライト、一つ一つ切り出す。フォーマットもSVG、PNG、Fontなどなど。 実装方法はいろいろありますし、サイト規模や運用体制などでどれが扱いやすいかは変わってきますよね。
たかがアイコン、されどアイコン。
利用頻度が高く、大きさや色が微妙に違ったりすることも多くてこれまでいろいろと試行錯誤してきました。 そこで今回はLIFULL HOME'Sでどんなアイコン実装が行われてきたかを振り返ってみたいと思います。
これまでの歩み。
都度一つ一つ切り出しての対応
15年ぐらい前、このころはデザインが上がってきたら一つ一つ切り出して対応していました。
共通化はしていても色が違う、大きさが違うとなれば都度切り出して作成が必要でした。
すでにあるこの大きさじゃダメなのなんて思いながら渋々切り出して対応していたものでした。
スプライト画像での対応
11年ぐらい前、リニューアルを機に新たに設計していく中でアイコンはスプライト画像化を選択しました。
ui-icon
という汎用classを作り、アイコンの指定にはもう一つ ui-icon-hoge
のように一意のclassを作って対応しました。
すでに作られた画像であれば <span class="ui-icon ui-icon-hoge"/>
という感じでHTMLを書けば良かったので格段に楽になりました。
大きさも文字サイズ連動するように作っていたのでフォントと同じように扱えたのも良かった点でした。
また、画像置換の手法を入れていたので空要素としてだけでなくテキストを含めることもできたので、アクセシブルな作りでもありました。
難点は色が違うアイコンだけはスプライト画像を修正して追加しなくてはいけなかったことでした。
// CSS .ui-icon { display: inline-block; width: 1em; height: 1em; line-height: 1; background: url('sprite_icon.png') no-repeat 0 0 / 1em 54em; text-indent: 1em; overflow: hidden; white-space: nowrap; } .ui-icon-history { background-position: 0 0 } .ui-icon-favorite { background-position: 0 -1em }
SVG symbolを利用した対応
7年ぐらい前、このころになるとブラウザのSVG対応も安定してきたのでSVGを活用し始めました。
具体的には、<body>
直下に利用するSVGのsymbolを置き、利用する箇所でSVG useして利用するというものでした。
SVGなので大きさだけでなく色に関しても可変性があり、ここでついに色・サイズ違いの再作成という呪縛から解き放たれました。
利用する場合にはSymbol IDと色、大きさを指定するだけのtwig macroを準備したのでそれを利用するだけとお手軽になりました。
inline SVGだからこそできるCSSからの色指定も重宝しましたね。
今現在もこれが現役で利用されています。
// Twig 内での利用方法 {{ svg.use('history', '32', '#000', 'title') }}
// Twig macro {% macro use(id, size, fill, title) %} {% if title %} {% set aria %}role="img"{% endset %} {% else %} {% set aria %}aria-hidden="true"{% endset %} {% endif %} <svg width="{{ size }}" height="{{ size }}" {{ aria }} focusable="false"> {% if title %} <title>{{ title }}</title> {% endif %} <use xlink:href="#{{ id }}" aria-hidden="true" {{- fill -}}></use> </svg> {% endmacro %}
とはいえ課題も出てきました。
運用としては必要なsymbolのみを必要なページで埋めるということをしてきました。 ところが、ページも増え、長年の運用による複雑さも増してきた中でどこのページにどのsymbolが必要なのかが把握できなくなってきたのです。
symbolを置く場所と利用する場所の距離が離れているのが一つの原因でした。
利用しているコンポーネントが読み込まれたらsymbolを埋め込む等ができたら良かったのですが、それもそう簡単ではないので別の方法を準備することにしました。
data-url化したSVGをimgタグのsrcに埋める対応
そして今。上記課題を解決しつつ、作り上げた資産(SVG)も有効活用できる方法として、SVGをdata-urlに変換してimgタグのsrcに埋めるmacroを新たに作りました。
これによりsymbolの読み込みが不要になり、上で挙げた課題解決への一助となる狙いです。
課題解決への一助と書いたのは、既存のsymbol利用をやめるわけではなく併用するからです。
当初は既存macroを書き換えてしまおうと考えていたのですが、symbol利用で書いた通りCSSからの色指定は重宝しているのでこれをなくすことはせず、併用という道を取りました。
// Twig macro {% macro img(id, size, color, alt) %} {% import _self as svg -%} {% set replaceMap = { '<': '%3C', '>': '%3E', '#': '%23', '{': '%7B', '}': '%7D', ' ': '%20', '"': "'", } -%} {% set src = 'data:image/svg+xml,' ~ svg.svg(id, size, color)|replace(replaceMap) %} <img src="{{ src }}" width="{{ size }}" height="{{ size }}" alt="{{ alt }}"> {% endmacro %} {% macro svg(id, size, fill) %} <svg xmlns="http://www.w3.org/2000/svg" width="{{ size }}" height="{{ size }}"> <def> {% include 'Bundle:Assets:svg/icon/_' ~ id ~ '.svg.twig' %}{# svg symbol file #} </def> <use href="#{{ id }}" {{- fill|raw -}}></use> </svg> {% endmacro %}
番外編:SASSでSVGをdata-url化してbackground-imageで読み込む
実はこのdata-url化という手法は以前からSassで利用していました。
Twig同様にSVGコードを渡したらdata-urlに変換して返す関数を作って実現しています。
SVGコードをencodeして data:image/svg+xml
で返す。
TwigでもSassでもやっていることは一緒ですね。
// Sass @function svgUrlEncode($svg) { $replaceMap: ( '<': '%3C', '>': '%3E', '#': '%23', '{': '%7B', '}': '%7D', ' ': '%20', "'": '"', ); @each $s, $r in $replaceMap { $encode: string.str-replace($s, $r, $svg, true); } @return $encode; } @function svgDataUri($svg) { $svg: svgUrlEncode($svg); @return url('data:image/svg+xml;charset=utf-8,#{$svg}'); }
終わりに
いかがでしたでしょうか?
やり方はほかにもいろいろありますが、言語やブラウザの対応状況等のタイミング、使える時間というのもあってこういった変遷をたどってきました。
技術の進歩やその時の課題・状況に合わせ最適解を探す。
今後も課題解決をしながらサイト機能や開発効率などの改善に取り組んでいきたいと思います。
最後に、LIFULL ではともに成長していける仲間を募集しています。よろしければこちらのページもご覧ください。