🎭

SVGアイコン管理を楽にする自作ライブラリ「nubui」を作った話

に公開

SVGアイコン管理の課題

WebプロジェクトでSVGのアイコンを使う時、管理方法とかって結構悩みませんか?

  • デザイナーから独自SVGアイコンが提供される
  • 一部はFont AwesomeやMaterial Iconsなど既存のアイコンライブラリから流用
  • プロジェクトごとに管理方法がバラバラ
  • 色変更のたびにSVGを編集したり、CSS書いたり...

特に中小規模のプロジェクト(10-50個程度のアイコン)では、大きなアイコンライブラリをそのまま導入するのも過剰だし、かといって毎回手動管理するのも面倒。Reactなら専用ライブラリがありますが、AstroやVueとか、プレーンHTMLでも、特にライブラリに依存せずに使える統一的な解決策がほしい…。

私は最近まで postcss-inline-svg(詳しくは【postcss-inline-svg】Sassを使いつつPostCSSでSVGをCSSでもインライン化してみた - Photosynthesic blogを参照) を使ったりしていましたが、Tailwindとの相性やmask-imageの普及を考えて、もっとシンプルなアプローチがいいな、ということで、mask-imageベースに切り替えることにしました。

いくつか既存のライブラリもあるようなのですが、私にとってはあまりしっくり来るものが見つからなかったので、毎度、今回Iconどうするかなあと考えるのも面倒になってきて、いっちょ自前のnpmを作ってみることに。

調査中に @choiceform/tailwindcss-svg-icon - npm という、非常に似た機能を提供するライブラリを発見しました。Base64エンコード、カスタムSVGファイル管理、Tailwind統合。なのでTailwindだけで使うし、という場合はTailwindのプラグインとして使えるのでそっちで全然いいかも。ただクラス名がicon-[folder/iconName]みたいになってるのでちょっと使いづらい?かもなあと思ったりもしました。

nubui: mask-imageベースのアイコン管理ツール

さてそんなわけで作ったnubuiというツールですが、お好きなアイコンデータから、mask-icon-xxxx というクラスを生成します。Tailwind 4+ と使いやすいようにしましたが、別に他のCSSと使っても大丈夫かなと思います。使う人がもしいたら、mask-icon-の部分好きなprefixに置き換える機能付けようかな、と思いますが、今のところ付けてません。

基本的なワークフロー

1. SVG ファイルを配置

src/assets/icon/以下にSVGファイルを配置します。
ファイルの縦横は8の倍数の正方形で(24px四方など、既存のアイコンライブラリと揃えています)。
ディレクトリは自由に設定できます。

2. CLI で自動生成

npx nubui build

実行すると:

  • SVG を最適化(SVGO) --- オプションでオフにもできます
  • 最適化したSVGを出力
  • Base64 エンコードし、Tailwind CSS の命名に合わせたmaskクラスを生成しCSSファイル化
  • ブラウザでアイコンの一覧をプレビュー表示、コピペでコード取得可

3. HTML で使用

<span class="mask-icon-heart w-6 h-6 text-red-500"></span>

みたいに書いて使います。Tailwind 4+ で使いやすくしていますが、普通のCSSに混ぜ込んでも使えると思います。

Base64埋め込みを選んだ理由

いまのところ、base64形式にしてファイルデータをCSSに埋め込む形にしています。

.mask-icon-heart {
  mask-size: contain;
  mask-repeat: no-repeat;
  mask-position: center;
  background-color: currentColor;
  mask-image: url('data:image/svg+xml;base64,PHN2ZyB...');
}

data URLでSVGを埋め込む方法はいくつかありますが、Base64を選択した理由は:

  • URLエンコード: ブラウザサポートが不安定、SVG内の特殊文字処理が複雑
  • 生のSVG: data URL仕様違反でブラウザ依存、SVG内にカンマがあると壊れる
  • Base64: ファイルサイズは大きくなるが、最も確実で安全

メリット:

  • ネットワークリクエスト不要
  • すべてのアイコンが事前読み込みされる

デメリット:

  • Base64埋め込みによるCSSサイズ増加(50個で約50KB)

今後、これだと重くなったりするケースがもし出てきたら他の方法でも作れるようにしようかなあと思ったりもします(一回作りかけた)が、そうそうなさそうな気もするので一旦これで使ってみようと思います。時間ができたらベンチマークテストとかをしてみたい。

3つの出力モードについて

nubuiでは用途に応じて3つのレンダリングモードがあります。基本はmaskですがSVGタグとimgタグでも使えるようにしておきました、一応。

Mask Mode(推奨・デフォルト)

<span class="mask-icon-star w-6 h-6 text-yellow-500"></span>
  • currentColor対応でTailwindのtext-*クラスなどで色指定
  • 疑似要素でも使用可能

Inline Mode

<svg xmlns="..." viewBox="0 0 24 24" class="w-6 h-6">
  <path fill="currentColor" d="..."/>
</svg>
  • SVG要素として直接出力
  • 個別パスのアニメーションや操作が可能

IMG Mode

<img src="src/assets/icon/format/star.svg" alt="star" width="24" height="24" />
  • 外部SVGファイル参照
  • ブラウザキャッシュ効率が良い、色変更は不可

既存ソリューションとの比較

比較表があった方が説得力があるとAIに言われたのでそれっぽいのを入れておきます。

方法 メリット デメリット
既存アイコンライブラリ
(Font Awesome、Material Icons、Lucideなど)
豊富なアイコン、統一されたデザイン カスタムアイコンとの混在時に管理が分散
postcss-inline-svg ビルド時最適化 mask-image + currentColorが使える今では不要な手法
手動inline SVG管理 SVG属性の完全制御、個別パスのアニメーション可能 管理が煩雑、重複コード、HTMLサイズ増大
nubui 3つのモード(mask/inline/img)で用途別使い分け、ワンコマンドでプレビュー付き生成、フレームワーク非依存 Base64埋め込みによるCSSサイズ増加(50個で約50KB)

うーん。なんかリストが足りない気はする。

実装例

ネイティブのHTMLやCSS

基本的にはHTMLで <span class="mask-icon-xxx"> みたいに使うだけですが、コンポーネント化すると便利です。

Astro コンポーネントとして使う

例えばこんな感じで…

---
// src/components/Icon.astro
interface Props {
  name: string;
  size?: "sm" | "md" | "lg";
  color?: string;
}

const { name, size = "md", color = "currentColor" } = Astro.props;

const sizeMap = {
  sm: "w-4 h-4",
  md: "w-6 h-6",
  lg: "w-8 h-8",
};
---

<span class={`mask-icon-${name} ${sizeMap[size]} ${color}`}></span>

使用:

<Icon name="heart" size="md" color="text-red-500" />
<Icon name="star" size="lg" color="text-yellow-500" />

別に同じようにReactでもVueでも使えると思いますが、とりあえずAstroの例だけ書いておきます。
before/after疑似要素で使うのも良し…というか私はそっちを使う事が多いかもしれない…

今後の予定など

これからやること。

  • Base64埋め込みのファイルサイズ影響について、実際のプロジェクトでベンチマークテストを実施予定。アイコン数やファイルサイズに応じた推奨事項をドキュメント化したい。
  • プロジェクト内で実際に使われていないアイコンファイルを検出する機能。デザイン段階で入れてみたけど使ってないアイコン、地味に発生するよね。
  • 好きなprefixに置き換える機能。要望があれば…たぶん。

最初はオレオレButtonとIconだけのUIをライブラリ化しようかとか思ってたんですが、やっぱり各プロジェクトで最適解が変わるので、目的を絞っていろんなケースで使えるSVGアイコンの管理ツールとしてみました。npmのパッケージ作るの初めてだったのでそれも良い経験でした。

AIでなんでもできる!と言われてしまう昨今、SVGアイコン管理みたいな小さなことをウジウジ悩んでる人ってあんまりいないかも知れないけれど、どこかの誰かに届くと良いな。
もしよかったら試してもらってGitHubにフィードバックをいただけると嬉しいです。
(まだ全然実地テストが足りてないと思うので、ちゃんと動かなかったらすみません…可及的速やかに修正します!)


GitHub: https://github.com/photosynthesic/nubui
npm: https://www.npmjs.com/package/@photosynthesic/nubui

Discussion