LIFULL Creators Blog

LIFULL Creators Blogとは、株式会社LIFULLの社員が記事を共有するブログです。自分の役立つ経験や知識を広めることで世界をもっとFULLにしていきます。

UIアイコンの管理と実装をラクチンにする社内ツールとCDN

フロントエンドエンジニアの嶌田です。

アイコンは、UIデザインにおいて欠かせないパーツです。弊社が提供する不動産情報サイトであるLIFULL HOME'Sでも、多数のアイコンが使われています。

ウェブページにアイコンを埋め込む手段は無数にあります。あなたはいくつ言えますか? それぞれのメリットとデメリットについて説明できるでしょうか?

この記事では、LIFULL HOME'Sのフロントエンドで選ばれているアイコン表示方法およびその理由を説明します。その後、アイコンの管理や実装にまつわる不便な点をサービス横断的に解消するために作られた社内ツールと配信システムである、LIFULL Icon CDNを紹介したいと思います。

私たちのアイコン実装方法

無数にあるアイコン実装方法のうち、私たちが選んだ実装方法は極めてシンプルです。

<img src="assets/icon-star.svg" width="24" height="24" alt="">

そう、img要素を使うのです。これまでさまざまなアイコン実装方法を試してきましたが、結局これが一番だろうということになりました。

なぜなのか? いくつかの観点から見てみましょう。

極めて手軽に使える

アイコンを使いたい場面でimg要素を埋め込むだけなので、事前の準備や仕組みの構築が不要です。ブラウザがHTMLをパースしながらimg要素に出会うと、外部リソースとして画像データを読みにいき、自動的にその部分にはめ込んで表示してくれるのです。当たり前ですね!

SVGスプライト *1 のようなテクニックは、ページ内で使う可能性のあるアイコンをあらかじめひとまとめにしておく必要があります。不要なアイコンを読み込まないようにする等の最適化を想定すると、トリッキーな仕組みを構築しなければならないでしょう。

img要素を埋め込むだけなら、どのプロダクトでも極めて手軽に使えるのです。

十分なパフォーマンス

表示速度は十分に速いです。img要素のぶんだけ別々にHTTPリクエストが発行されることから、パフォーマンスがよくない印象を持たれるかもしれませんが、実際に速いです。

リクエスト数が増える問題は、HTTP/2のストリーム多重化により気にする必要がなくなっています。インラインSVG *2 に比べると、画像のデコード処理を別スレッドに逃がすことができるので、メインのHTMLのパースを素早く終えられるというメリットもあります。

標準的なHTTPキャッシュの恩恵を受けられることも大きな利点です。2度目の表示にはブラウザキャッシュが参照され、ネットワークリクエストは発生しません。

また、loading="lazy"属性を使った最適化も期待できるでしょう。アイコンは得てして装飾的に使われるので、読み込みを遅延させても問題ない場面が多いでしょう。

誰にとっても分かりやすい

どストレートなやり方なので、HTMLのごく基礎的な知識さえあれば仕組みを理解できます。エンジニアと他職種との連携の円滑さにもつながるはずです。

アクセシビリティ

アイコンの代替テキストは、imgタグのalt属性によって提供します。まったくひねりのない方法ですが、問題なく動作します。

アイコンは、実装方法によってはアクセシビリティ上の問題を引き起こしてしまうことがあります。

たとえばアイコンフォントは、フォントデータが読み込めなかったときやユーザーが意図的にフォントを上書きしていた場合、アイコンが表示されなくなってしまう場合があります。SVGスプライトやインラインSVGでのアプローチは、代替テキストの指定に難があり、幅広いサポートを目指すためにWAI-ARIAに頼らざるを得ない場合があります。

他にも、OSのハイコントラストモード(強制カラーモード)への対応や、色反転モードに対する対処が望まれます。それについては記事の後半で触れたいと思います。

色の変更

SVGスプライト、インラインSVG、アイコンフォント *3 の利点は、色変更がCSSで手軽にできることでした。img要素に戻るのは、色変更の手軽さを放棄することにならないでしょうか?

どうということはなく、私たちはこのようにします。

<img src="assets/icon-star--red.svg" width="24" height="24" alt="">

色を変えたアイコンを読み込めばよいのです。

あっ、まだブラウザの戻るボタンは押さないでください。本気か?と思うかもしれませんが、本気です。たしかに手軽とはいえないでしょう。使う色が増えるたびにファイルを増やさなければいけません。

でもたとえば、クエリパラメーターで色が指定できたらどうでしょう?

<img src="assets/icon-star.svg?color=ff0000" width="24" height="24" alt="">

裏側の仕組みはともかく、少なくとも色の指定はやりやすくなりそうですよね。

アイコンをもっと手軽に使いたい!

正直なことを言うと、マークアップエンジニアにとって、アイコンは非常に面倒くさいです。まじめに取り組むと、アイコンを書き出して最適化し、配信の仕組みに乗せ、アイコン用コンポーネントを実装しなければなりません。いずれの工程もノウハウの塊です。

LIFULLは多数のサービスを提供しています。不動産情報サイトのLIFULL HOME'Sだけをとっても、いくつものサービスの集合体です。これら一つひとつのサービスごとに、別々のエンジニアがアイコンのお決まり手順を踏んでいると考えると、とても非効率な気がしませんか。

そこで私たちは、アイコンにまつわるこれら実装上の課題をサービス横断的に解消するために、LIFULL Icon CDNと呼ばれる仕組みを構築しました。

LIFULL Icon CDNの特色

LIFULL Icon CDNは大きく分けて2つのコンポーネントで構成されています。

APIと呼ばれるコンポーネントは、ドメイン名を持ち、エンドユーザーからのリクエストに応えます。前段にCDN *4を配置しており、静的キャッシュを持つことで高速化とオリジンサーバーの負荷軽減をしています。

Appと呼ばれるコンポーネントは、社内向けのアイコン管理アプリケーションです。外部からのアクセスはできません。アイコンを一覧し、必要なアイコンをページに埋め込むためのコードを取得することができます。

ここからは、スクリーンショットをまじえつつ、LIFULL Icon CDNでどんなことができるのかを見ていきます。

定義済みアイコンを一覧できる

LIFULL Icon CDNが取り扱うアイコンは、デザインガイドラインで定義されたアイコンです。プロダクト横断的に利用されることが想定されたアイコンがずらっと並んでいます。執筆時点で300余りのアイコンが登録されています。

LIFULL Icon CDNの管理アプリケーション(App)のスクリーンショット。アイコンが一覧されている

開発者は、この中から使いたいアイコンを選びます。

埋め込みコードをコピーできる

詳細ページからはエンドユーザーがアイコンにアクセスするためのURLと、埋め込むためのコード(HTML形式およびJSX形式)が取得できます。

日本地図アイコンの詳細ページのスクリーンショット

アイコンをサービスで利用するための開発者の手順はとてもシンプルです。HTMLのコードをコピーして、コードに貼り付ければ終わりです。アイコン利用のための事前準備は不要です。

色を自由に変更できる

アイコンの詳細ページ上からアイコンの色を設定できます。色を変更するとクエリパラメーターに反映され、指定した色のアイコンのURLが取得できます。

たとえば日本地図のアイコンはデフォルトでオレンジと黄色の2色から構成されていますが、黒に塗りつぶしたり、

https://icon.lifull.com/lh/japan-map-twotone?fill=black

黒で描画された日本地図のアイコン

2色を別々に塗り替えることも可能です。

https://icon.lifull.com/lh/japan-map-twotone?modify[yellow]=mono-300&modify[orange]=accent-blue

青と灰色で描画された日本地図のアイコン

無秩序に使われることを避けるため指定できる色には制約があり、デザインガイドラインで定義された色トークンの中から選定する仕様にしています。

高パフォーマンス

配信用アプリケーションの前段にCDN(Amazon CloudFront)を設け、静的キャッシュを担わせることで、ほとんどすべてのリクエストをCDNから応答するようにしています。そのため動作は非常に軽快です。

各アイコンのSVGデータはオブジェクトストレージ(Amazon S3)に保管されていますが、すべての色指定に対する物理的なSVGデータが準備されているわけではありません。アプリケーション層で応答時に色変換の処理を適用しています。CDNには処理後のSVGレスポンスをキャッシュさせているため、軽快な動作を保っています。

アクセシビリティを高める工夫

いくつかの配慮を加えることでアイコンの使いやすさが向上します。

1つは、強制カラーモードへの配慮です。WindowsにはOSレベルの強制カラーモード、いわゆるハイコントラストモードが搭載されています。有効にすると、ウェブページを含むOS全体の配色が事前に定義された色で描画されるようになります。

img要素を通じて埋め込まれている画像は、デフォルトでは強制色が適用されません。一方、アイコンの背景色は透過されているため、たとえば黒基調の強制色における黒いアイコンは背景に溶け込んで見えなくなってしまうという問題が起こります。

2点目は、色反転モードへの配慮です。特に配慮が必要なのはスマート反転モードと呼ばれるもので、macOSやiOSなどのAppleのOSに搭載されています。スマート反転モードでは、OS全体の色をネガポジ反転しますが、画像や動画は反転されない挙動をとります。

この場合も、たとえば白の背景に置かれた黒のアイコンは、スマート反転モードを適用すると、背景色のみが反転するため、背景に溶け込んで見えなくなってしまう状況に陥ってしまいます。

2つの問題に対処するため、各SVGファイルには次のようなCSSを宣言しています。

<svg xmlns="http://www.w3.org/2000/svg" ...>
  <style>
    @media (forced-colors: active) {
      @media (prefers-color-scheme: dark) {
        [fill]:not([fill="none"]) { fill: #fff !important }
      }
      @media (prefers-color-scheme: light) {
        [fill]: not([fill="none"]) { fill: #000 !important }
      }
    }
  </style>
  ...
</svg>

そして、LIFULL Icon CDNを使用する側には次のようなCSSを適用します。

/* 強制カラーモードが有効なとき、img要素経由で読み込まれるSVGに
   prefers-color-schemeメディアクエリが引き継がれない問題がある。
   SVG側からカラースキーム文脈を取得するために、以下のスタイルを
   アイコン読み込み元に記述する必要がある。 */
@media (forced-colors: active) {
  @media (prefers-color-scheme: light) {
    img[src^="https://icon.lifull.com/"] {
      color-scheme: light;
    }
  }
  @media (prefers-color-scheme: dark) {
    img[src^="https://icon.lifull.com/"] {
      color-scheme: dark;
    }
  }
}

/* macOSのスマート反転モードが有効のとき、img要素は反転対象から除外される。
   背景が透過されているアイコンが色反転から除外されると意図せずコントラストが
   不足する可能性があるため、以下のスタイルにより反転に含まれるようにする。 */
@media (inverted-colors: inverted) {
  img[src^="https://icon.lifull.com/"] {
    filter: invert(0);
  }
}

細かく書くと1記事分の分量になってしまうため省略しますが、ブラウザ側の不具合に対処しつつ、LIFULL Icon CDNを使うimg要素にだけアクセシビリティの向上のためのスタイルを適用しています。

デザイン上の工夫

LIFULL Icon CDNはエンジニアのデザイナーとエンジニア発案のプロジェクトとして始まりましたが、デザイナーも巻き込み、アイコンの統制や管理の仕組みを含む全体的なプロジェクトになりました。その意味ではアイコンのデザインシステムと呼べるものかもしれません。

デザイナーからみたLIFULL Icon CDNについては、別記事として公開されています。ぜひご一読ください!

note.com

おまけ 実装方法のバリエーション

以下はLIFULL Icon CDNのドキュメントに記載されている内容で、アクセシビリティに配慮したいろいろな実装パターンを解説しています。

もっとも基本的な使い方

アイコン詳細ページで取得できるHTMLをコピーして、そのままプロダクトのコードに貼り付けます。LIFULL Icon CDNチームはこれがベストプラクティスであると考えており、推奨しています。

<img src="https://icon.lifull.com/l/chevron-right" width="24" height="24" alt="" />

使用例1:ボタン内のアイコンとして使用する(装飾としての利用)

テキストラベルの横に配置される場合や、単なる装飾としてのアイコン利用の場合、代替テキストは不要です。alt属性は空文字列に設定してください。なお、alt属性自体は省略しないでください。

<button class="flex items-center rounded border border-mono-400 bg-white px-4 py-2">
  <span>次へ</span>
  <img src="https://icon.lifull.com/l/chevron-right" alt="" width="24" height="24" class="ml-2 size-4">
</button>

使用例2:ボタン内のアイコンとして使用する(機能ラベルとしての利用)

テキストラベルが併記されず、アイコンがボタンやリンク内における唯一の要素である場合、代替テキストが必要です。alt属性に機能や状態を表す代替テキストを指定してください。

<button class="flex items-center rounded border border-mono-400 bg-white p-3">
  <img src="https://icon.lifull.com/lh/star-twotone?fill=orange" alt="お気に入り" width="56" height="56" class="size-6">
</button>

使用例3: 背景画像として利用する(非推奨)

背景画像には代替テキストが設定できず、強制カラーモードで非表示になってしまうため非推奨ですが、装飾としての用途に限っては使用しても構いません。

<button class="button">次へ</button>

<style>
  .button {
    min-height: 24px;
    padding-right: 32px;
    background: url('https://icon.lifull.com/l/chevron-right') no-repeat right center / 24px;
  }
</style>

使用例4:インタラクションに応じて色を切り替える

シンプルさを重視し、切り替え前後のアイコンをあらかじめ埋め込んでおくアプローチを推奨します。

以下は、ボタンへのマウスオーバーで色が若干濃くなるアイコンのコード例です。

<button class="group flex items-center rounded border border-mono-400 bg-white p-3">
  <img src="https://icon.lifull.com/lh/star-filled" alt="お気に入り" width="56" height="56" class="size-6 group-hover:hidden">
  <img src="https://icon.lifull.com/lh/star-filled?fill=orange-800" alt="お気に入り" width="56" height="56" class="hidden size-6 group-hover:block">
</button>

使用例5:任意の色を使用する

原則として、定義済みのカラートークンを使用します。どうしても任意の色を使用する場合は、mask-imageプロパティを使うテクニックが使用できます。

<span
  class="mask-icon"
  style="
    background-color: rebeccapurple
    --mask-icon: url('https://icon.lifull.com/lh/star-filled');
  "
></span>
          
<style>
  .mask-icon {
    display: block;
    width: 24px;
    height: 24px;
    mask: var(--mask-icon) no-repeat center / 100%;
  }
</style>

Tailwind CSSを使う場合は以下のようになります。

<span
  class="block size-6 bg-[rebeccapurple] [mask:var(--mask-icon)_no-repeat_center/100%]"
  style="--mask-icon: url('https://icon.lifull.com/lh/star-filled')"
></span>

次期バージョンも進行中

LIFULL Icon CDNはすでに社内エンジニアに便利に使われていますが、社内向けアプリケーションの機能拡充が予定されています。

現在のアイコンの追加・更新作業は、AWSコンソールにログインしS3に直接アップロードする運用フローになっています。かかる手間やヒューマンエラーのリスクに対処するために、管理アプリケーション上から直接アップロードや更新等の作業が行えるようになっていく予定です。

クレジット

<LIFULL Icon CDN運営チーム>

LIFULL HOME'S事業本部 エンジニア
Dongjae Park、Shin Mingyu
クリエイティブ本部 デザイン部
ぼこ、URITA RIKI
スペシャルサンクス
鄭 在淳、曺 承鉉、そめ

お知らせ

LIFULLは主体性を重んじる社風で、LIFULL Icon CDNのようなエンジニア発案のプロジェクトが多数進行しています。他職種の協力も手厚いです。転職先に弊社をぜひご検討ください!

hrmos.co

hrmos.co

*1:SVGスプライト……ページ内で使うアイコンセットを事前にsvg要素でまとめて定義しておき、利用箇所でフラグメント参照を使って手軽にアイコンを使えるようにする仕組み。CSSで色変更がしやすい利点がある。https://css-tricks.com/svg-sprites-use-better-icon-fonts/

*2:インラインSVG……svg要素を使いSVG画像をHTMLにインラインで記述すること。CSSで色変更がしやすい。

*3:アイコンフォント……アイコンデータをフォントファイルの字形データとして格納し、文字としてアイコンを表示するテクニック。CSSで色変更がしやすい。

*4:CDN……Content Delivery Network。ウェブコンテンツをユーザーに高速で配信するために、世界中の複数のサーバーにデータを分散させる仕組み。