Moddableでアイコンを手軽に表示したい
ModdableでUIを持つアプリケーションを作るあたり、アイコン的なものを表示したいケースがある。
単品のアイコンであれば画像アイコンをPNGなりで読みこむか、スプライトな画像を作ってTextureでvariantで表示を切り替えるという方法が従来からあるが、今回ある程度の数を持ったアイコンセットを手軽に扱いたいケースを考える。
fontbmでアイコンフォントを使う
Webで手軽にアイコンを表示したい場合に使うのはアイコンフォント。対象アイコンのコードポイントとして文字を表示をすればよいので扱いは容易。 ただし、数百もアイコンがあるフォントファイルとなるとマイコン向けのリソースとしては容量が大きいので、必要分に絞って容量を削減しつつ使いたい。
ということで使うのは昨年サポートされた(fontbm)https://github.com/vladimirgamalyan/fontbm/を使う。
fontbmでは、ttfファイルをModdableで使用するfntとpngを生成するコマンドラインツール。Moddableではmanifestファイルにfontbmの設定を書くことでビルド時に自動生成させる仕組みが導入された。詳細はModdableのBlogでも紹介されている。
今回はアイコンセットとしてI(coMoon)https://icomoon.io/を使用した。今回の手順に従って生成されたアイコンの再配布については、IcoMoonの再配布の条件に従ってくださ。
fontbm導入
通常のModdableセットアップ手順には含まれていないため、別途インストールする必要がある。
Windowsであれば実行可能形式で配布されており、Mac/Linuxでは自前でビルドする必要がある。手順については割愛。
その後、FONTBM
の環境変数を設定する。なお、これはfontbmのフォルダではなく、 fontbmのプログラムそのもののパス の指定となるため注意。
manifest.jsonでfontbmを設定する
今回使用したアイコンフォントは(IcoMoonのFree Version)(https://icomoon.io/#icons-icomoon)でアイコン数は490。ファイルサイズとしては127KB
だった。
指定としては、以下のように対象のアイコンファイル、出力のサイズ、対象の文字(今回は適当に選んだ5個)を指定する。
"resources":{ "*-mask": [ { "source": "./IcoMoon-Free", "size": 28, "characters":"\ue900\ue905\ue90f\ue911\ue912" } ] }
これでビルドすると、($MODDABLE)//build/tmp/mac/mc/debug/($application)/resources/
にfontbmで変換されたフォントファイルが生成される。
リソースとして読み込む.fnt
と.png
のファイルサイズが小さくなっていることが確認できる。
~ ll ~/Projects/moddable/build/tmp/mac/mc/debug/text/resources/IcoMoon-* -rw-r--r-- 1 satoshi staff 2.6K 1 8 22:21 /Users/satoshi/Projects/moddable/build/tmp/mac/mc/debug/text/resources/IcoMoon-Free-28-alpha.bmp -rw-r--r-- 1 satoshi staff 1.0K 1 8 22:21 /Users/satoshi/Projects/moddable/build/tmp/mac/mc/debug/text/resources/IcoMoon-Free-28.bf4 -rw-r--r-- 1 satoshi staff 186B 1 8 22:21 /Users/satoshi/Projects/moddable/build/tmp/mac/mc/debug/text/resources/IcoMoon-Free-28.fnt -rw-r--r-- 1 satoshi staff 146B 1 8 22:20 /Users/satoshi/Projects/moddable/build/tmp/mac/mc/debug/text/resources/IcoMoon-Free-28.json -rw-r--r-- 1 satoshi staff 1.9K 1 8 22:21 /Users/satoshi/Projects/moddable/build/tmp/mac/mc/debug/text/resources/IcoMoon-Free-28.png -rw-r--r-- 1 satoshi staff 15B 1 8 22:20 /Users/satoshi/Projects/moddable/build/tmp/mac/mc/debug/text/resources/IcoMoon-Free-28.txt
あとは通常のPiuアプリケーションにて、対象のアイコンをUnicode文字参照として記載すればアイコンが表示される。
const blackSkin = new Skin({ fill: "black" }); const whiteSkin = new Skin({ fill: "white" }); const textStyle = new Style({ font: "IcoMoon-Free-28", left: 10, right: 10, top: 15, bottom: 15, }); const centerStyle = new Style({ horizontal: "center" }); let TestApplication = Application.template(($) => ({ skin: blackSkin, style: textStyle, contents: [ Text($, { left: 0, right: 0, top: 0, skin: whiteSkin, style: centerStyle, blocks: [{ spans: ["\u{e900}\u{e905}\u{e90f}\u{e911}\u{e912}"] }], }), Icon({ icon: "home", left: 0, top: 100, size: 32, color: "white" }), Icon({ icon: "pencil", left: 40, top: 100, size: 48, color: "green" }), Icon({ icon: "camera", left: 90, top: 100, size: 56, color: "yellow" }), Icon({ icon: "music", left: 150, top: 100, size: 24, color: "red" }), Icon({ icon: "play", left: 180, top: 100, size: 32, color: "purple" }), ], }));
アイコンフォントをfontbmで変換して文字として表示することができた。 #moddable pic.twitter.com/Ap267PLi1b
— STC (@stc1988) 2023年1月8日
Outlineを使ったSVG描画
これも(昨年追加された機能)https://blog.moddable.com/blog/scalableoutlines/で、Canvas互換のAPIでパスを描画できるようになり、SVGのパスも指定することでレンダリングができるようになった。 そのため、SVGで作られたアイコンがそればそのまま使うことができそう。
幸いにも(IcoMoonのカスタマイズアプリ)https://icomoon.io/appでアイコン単体のSVGをがダウンロードできるため、ここからパスを抜き出して使ってみる。
アイコンフォントでは文字として直接使うことができたが、Outlineの場合はパスの描画処理を書く必要があるため、アプリケーションから取り回ししやすいようにIcon Object
を定義してみた。
const iconMap = { home: "M512 295.222l-256-198.713-256 198.714v-81.019l256-198.713 256 198.714zM448 288v192h-128v-128h-128v128h-128v-192l192-144z", pencil: "M432 0c44.182 0 80 35.817 80 80 0 18.010-5.955 34.629-16 48l-32 32-112-112 32-32c13.371-10.045 29.989-16 48-16zM32 368l-32 144 144-32 296-296-112-112-296 296zM357.789 181.789l-224 224-27.578-27.578 224-224 27.578 27.578z", camera: "M152 304c0 57.438 46.562 104 104 104s104-46.562 104-104-46.562-104-104-104-104 46.562-104 104zM480 128h-112c-8-32-16-64-48-64h-128c-32 0-40 32-48 64h-112c-17.6 0-32 14.4-32 32v288c0 17.6 14.4 32 32 32h448c17.6 0 32-14.4 32-32v-288c0-17.6-14.4-32-32-32zM256 446c-78.425 0-142-63.574-142-142s63.575-142 142-142c78.426 0 142 63.575 142 142s-63.573 142-142 142zM480 224h-64v-32h64v32z", music: "M480 0h32v368c0 44.183-50.145 80-112 80s-112-35.817-112-80c0-44.184 50.145-80 112-80 31.342 0 59.671 9.2 80 24.020v-184.020l-256 56.889v247.111c0 44.183-50.144 80-112 80s-112-35.817-112-80c0-44.184 50.144-80 112-80 31.342 0 59.671 9.2 80 24.020v-312.020l288-64z", play: "M490.594 80.054c-71.816-10.325-151.166-16.054-234.593-16.054-83.43 0-162.778 5.729-234.597 16.054-13.765 53.863-21.404 113.375-21.404 175.946 0 62.57 7.639 122.083 21.404 175.945 71.819 10.326 151.168 16.055 234.597 16.055 83.427 0 162.776-5.729 234.593-16.055 13.766-53.862 21.406-113.375 21.406-175.945 0-62.571-7.64-122.083-21.406-175.946zM192.001 352v-192l160 96-160 96z", }; class IconBehavior extends Behavior { onCreate(shape, data) { var scale = data.size / 512; const path = Outline.SVGPath(iconMap[data.icon]); shape.fillOutline = Outline.fill(path).scale(scale); } } const Icon = Shape.template(($) => ({ left: $.left, top: $.top, width: $.size, height: $.size, skin: new Skin({ fill: $.color }), Behavior: IconBehavior, })); let TestApplication = Application.template(($) => ({ skin: blackSkin, style: textStyle, contents: [ Icon({ icon: "home", left: 0, top: 100, size: 32, color: "white" }), Icon({ icon: "pencil", left: 40, top: 100, size: 48, color: "green" }), Icon({ icon: "camera", left: 90, top: 100, size: 56, color: "yellow" }), Icon({ icon: "music", left: 150, top: 100, size: 24, color: "red" }), Icon({ icon: "play", left: 180, top: 100, size: 32, color: "purple" }), ], }));
SVGがあるなら当然Outlineで描画できる。こちらの方がサイズごとのアイコンも用意不要だし、プログラムからの取り回しもしやすい。
— STC (@stc1988) 2023年1月8日
アイコンフォントと共に書いたコードはこんな感じ。 #moddable pic.twitter.com/IfEY41Fy6D
まとめ
SVG描画の方が、リソースサイズも抑えられ、アプリケーションからの描画操作も容易そう。 一方で今回のサンプルではSVGパスをSVGファイルから転機した形となったため、もっと大量のアイコンを扱いたい際の事前orアプリケーション側での仕組みの検討が必要。