Webデザインといえば、今まで「Photoshop」「Illustrator」「Sketch」といった感じでしたが、昨今の「AdobeXD」の普及により、弊社でもXDで作成されたデザインを扱う機会が増えてきました。しかし、まだまだ発展途上のXDでは痒いところに手が届かない部分が多いのも事実です。
そこで、今回はXDで個人的に使い勝手が悪いと思っている「パスのアウトライン化」についての記事になります。
XDのアウトライン化は面倒くさい
そもそも「アウトライン化」とは?
例えば、デザインで使用されているフォントが見付からない場合、基本的にユーザーのパソコンに入っている代替フォントに置き換わります。リンクファイルの場合は、リンクが見付からずに正常に表示されないか、編集不可なデータに置き換わることが多いです。
※アウトライン化されていないデザインを複数のパソコンで見た時の比較
環境によってデザインの見え方が変わり正しく表示や書き出しができませんので、デザインの送付時にはアウトライン化(パスに変換)したり、リンクファイルを埋め込むのが基本となります。
PhotoShopの場合は、編集はできませんがフォントのスタイルを維持してくれたりと、デザインの形状は保てますのでPNGなどで書き出しが可能です。IllustratorとXDは残念ながら代替えフォントに置き換わってしまいました。
アウトライン化の手順について
XDは、アウトライン化したい要素を選択して「右クリック+パスに変換」または「ctrl+8(macの場合はcmd+8)」で変換可能です。リンクファイルからリンクを解除するには選択して「右クリックからグループ化を解除」または「ctrl+Shift+g」でリンクの解除が可能です。
一見すると、特に難しいことはないかと思いますが、「パスに変換」は変換する要素を直接選択する必要があります。そのため、グループの上からなどはパスに変換できません。
そのため、「グループ化しながらデザインを作成して、最後にアウトライン化!」と思ったときに、手動でグループの中に入り変換作業をおこなう必要があります。人の手でおこなう操作には必ずと言っていいほどミスが付きますので、デザインを送付しても「正しく表示されていなくて再送」ということにもなり得ます。
XDにアウトライン化を便利にするプラグインは無かった
そんなXDですが、プラグインが作成できるAPIを備えています。機能に拡張性を持たせることで、多くのプロジェクトで作業の効率化が図れます。
現在では数多くのサードパーティ製プラグインが開発されて、より柔軟なソフトウェアとして日々進化していると感じます。「これだけ多くのプラグインがあれば、アウトライン化を便利にするプラグインも誰か開発しているはず…」と思って探してみましたが、すぐには見付かりませんでした。
ということで、良い機会ですので勉強を兼ねて自分で作ってみることにしました。
今回は「AdobeXD バージョン:22.1.12.5」での開発になります。
今回の目標:グループの上からでも「パスに変換」を実行
今回の目標はグループの上からでも「パスに変換」を実行することです。
ついでに変換する要素を設定できたらなおよしとします。
事前準備
プラグインを作成するに当たって、公式のドキュメントを参考に進めてみます。
大まかな手順は以下になります。
- 1.XDを起動して左上のメニューから「プラグイン→開発版→プラグインの作成」を選択
- 2.ブラウザ(I/Oコンソール)が立ち上がるので「New Pulgin」を選択
- 3.プロジェクトの名前を入力し「Create Plugin」※を選択
- 4.「Download Starter Project」からファイル一式をダウンロード
- 5.XDのメニューから「プラグイン→開発版→開発フォルダーを表示」を選択
- 6.手順4でダウンロードしたファイルを解凍して、開発フォルダーに適当なディレクトリを作成し、移動
※「Create Plugin」で作成したプロジェクトはI/Oコンソールからは消せないようですので注意して下さい。
これで事前準備は完了です。
XDのプラグインには一意のIDが必要になりますが、I/Oコンソールからプロジェクトを作成すると勝手に付けてくれるので便利ですね。
01「manifest.json」と「main.js」を理解しよう
プラグインの作成の第一歩は「manifest.json」と「main.js」について理解することです。
解凍したファイルを例に見てみましょう。
[manifest.json]
1 2 3 4 5 6 7 8 9 | { "uiEntryPoints" : [ { "label" : "Create Rectangle", "type" : "menu", "commandId" : "createRectangle" } ], } |
[main.js]
1 2 3 4 5 6 7 8 9 10 | function rectangleHandlerFunction(selection) { ... } module.exports = { commands: { createRectangle: rectangleHandlerFunction } }; |
注目するのはmanifest.jsonの「”commandId” : “createRectangle”」とmain.jsの「commands: { createRectangle: rectangleHandlerFunction }」になります。
XDでプラグインが実行されると、commandIdに指定されているプロパティがキーとして実行されます。
この場合はmainのcreateRectangleのメソッドであるrectangleHandlerFunctionが実行されます。
プラグインの種類について
プラグインは大きく分けると「パネル」と「コマンド」の2種類が存在します。
パネル | 設定をおこなう項目を表示させることができるため、複雑な処理をおこなうのに優れている |
---|---|
コマンド | ショートカットで素早く実行できるため、簡単な処理を連続しておこなうのに優れている |
※パネルの設定画面(左)とコマンドの実行(右)
今回はパネルの使用は設定のみで、実行はコマンドでおこないますので、manifest.jsonとmain.jsの該当箇所は以下のようになりました。パネルについてもほぼcommandIdと同様の記述になります。また、ショートカットの設定も「manifest.json」でおこないます。
[manifest.json]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | { "uiEntryPoints" : [ { "label" : "アウトラインを作成", "type" : "menu", "commandId" : "outlinePath", "shortcut": { "mac": "Cmd+9", "win": "Ctrl+9" } }, { "label": "設定", "type": "panel", "panelId": "outlinePathMenu" } ], } |
[main.js]
1 2 3 4 5 6 7 8 9 10 | module.exports = { commands: { outlinePath: outlinePath.action }, panels: { outlinePathMenu: outlinePathMenu } }; |
今回は処理が複雑になりそうでしたので、jsはES6モジュールで記述しました。outlinePath、outlinePathMenuはどちらもインスタンスになります。メニューからoutlinePathを実行すると「outlinePath.action」が呼び出され、パネルに対しては「outlinePathMenu」の各メソッドが実行されます※
※パネルにはいくつかのライフサイクルメソッドが用意されていて、それぞれが決まったタイミングで実行されます。
show | パネルがメニューから実行されたとき |
---|---|
hide | パネルが非表示になるとき |
update | ユーザーが選択を変更したり、ノードを更新するとき |
それでは、ここまでで少し長くなってしまいましたので下記の要点に絞って解説します。
・【コマンド】jsで選択してアウトライン化する処理
・【パネル】vue.jsを使用した項目管理
・【パネル】設定ファイルについて
02【コマンド】jsで選択してアウトライン化する処理
outlinePathについて、こちらは主にメニューから実行した際の処理を記述しています。
XDのcommandsではメソッドに2つの引数を渡します。
[main.js]
1 2 3 4 5 | action(selection, root) { ... } |
「selection」は現在のカーソルの位置、「root」はドキュメントのルート(この場合は選択した要素のアートボード)を受け取ります。
今回は選択した要素の子要素を選択していくため、rootは使用しません。
パス化の手順
プログラムでアウトライン化をおこなうためには、前述したselectionでパスに変換したい要素を選択し、XDのcommands APIを使用してアウトライン化をおこないます。
[EventController.js]
1 2 3 4 5 6 7 | const commands = require('commands'); ~ // selection.itemsにアウトライン化したい要素を指定 selection.items = itemsArray(node, selection, this.settings); commands.convertToPath(); |
itemsArray関数内では最初に選択した項目から再帰的に子要素を選択していき条件に一致した項目を配列に入れて返り値としています。こちらが基本となります。
03【パネル】vue.jsを使用した項目管理
次に、条件を設定するパネルについてです。
公式ではReactを使用したパネルの作成について書かれていますが、せっかくなので今回はVueを使用します。まずはvueファイルを作成しましょう。項目はjsonで管理しますので、vueファイルは取得したデータをv-forで出力する形になります。
[App.vue]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <template> ~ <label v-for="item in itemGroup01" class="row label" :class="item.disabled === true ? 'isDisabled' : ''" > <input type="checkbox" v-model="item.prop" :disabled="item.disabled" @change.prevent="createSettings" > ~ </template> <script> export default { computed: { itemGroup02() { return this.items.filter((obj) => obj.type === 2); } } } </script> |
this.itemsにjsonファイルを入れ、filterで絞り込みをしています。
チェックボックスが変更されると、createSettingsメソッドが実行され、こちらで設定ファイルの更新をおこないます。
更新については後述しますので、パネルとコマンドの連携について説明します。
[App.vue]
1 2 3 4 5 6 7 8 9 10 11 12 13 | <script> export default { methods: { async createSettings() { ~ // EventControllerを更新 this.$parent.$emit('settings-event', this.items); } } } </script> |
[PanelController.js]
1 2 3 4 5 6 7 | <script> this.instance.$on('settings-event', (settings) => { this.EventController._setSettingsFile(settings); }); </script> |
初期化時とパネルが更新された時に、親のsettings-eventイベントが実行されます。
こちらでEventControllerに設定ファイルが渡されますので、itemsArray.jsで条件を調べる際に設定ファイルの情報を元に絞り込みが行われます。
04【パネル】設定ファイルについて
XDの設定ファイルの読み取り、書き込みにはUnified Extensibility Platform(UXP)のFile APIを使用します。
このAPIは、フォルダのパスを返したり、ファイルの読み込み・書き込みをおこなう際等に使用します。
今回使用した主な機能は以下になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | const fs = require('uxp').storage.localFileSystem; // 一時フォルダのディレクトリを返す const folder = await fs.getTemporaryFolder(); // 設定ファイル等を保存するディレクトリを返す const folder = await fs.getDataFolder(); // プラグインフォルダのディレクトリを返す(読み取り専用) const folder = await fs.getPluginFolder(); // フォルダー内のファイルまたはフォルダーのエントリーを返す const entry = await folder.getEntry(filePath); // フォルダ内に新規エントリーを作成 // overwriteをtrueにすると上書きが有効になります。 const entry = await folder.createEntry(fileName, { overwrite: true }); // ファイルの読み込み await entry.read(); // ファイルの書き込み await file.write(); |
参考:https://adobexdplatform.com/plugin-docs/reference/uxp/module/storage.html
読み取り
読み取りはパネル側とコマンド側で初期化の際におこないます。あらかじめプラグインフォルダに初期設定用のJSONファイルを用意しておき、もし設定ファイルが存在していなかったら初期設定用のファイルを読み込みます。(本来はプラグインのアップデートにも対応させるために既存と初期設定用を統合するのが正解かと思います。)
1 2 3 4 5 6 7 8 9 10 11 12 13 | const fs = require('uxp').storage.localFileSystem; const settings = require('./panels/settings.json'); try { const folder = await fs.getDataFolder(); const file = await folder.getEntry('settings.json'); const json = await file.read(); this.settings = JSON.parse(json); } catch(e) { this.settings = settings; } |
書き込み
Vue.jsでcheckboxの変更を監視し、createSettingsメソッドを実行。
変更時にファイルを更新し、同時にEventControllerにも値を渡しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | async createSettings() { try { const folder = await fs.getDataFolder(); const file = await folder.createEntry(this.file, { overwrite: true }); await file.write(JSON.stringify(this.items)); } catch(e) { console.error(e); } // EventControllerを更新 this.$parent.$emit('settings-event', this.items); } |
※参考ページにcreateEntryの記述が見当たりませんが、内容はcreateFileと同じ感じです。
下記のページに記述が存在したため、こちらを採用してます。
https://adobexdplatform.com/plugin-docs/reference/uxp/storage-index.html
※最初プラグインフォルダに設定ファイルを保存しようと思いましたが、読み取り専用のためこちらのメソッドから書き込むことは不可でした。
所感
今回色々と触ってみて、そもそもAPIとしてはまだまだ不完全と感じました。
例えば、要素のグループ化をおこなう際、スクリプトでおこなうとグループがアートボード直下に弾き出される…といったことが多々ありました。原因としては、複数の要素を順番にグループ化する際に選択がそのままだと、要素の持つ固有のIDのようなものが変更されてしまうため…といったものでした。
こちらはフォーラムでも少し話題になっていましたが、手動でおこなう選択とスクリプトでおこなう選択は必ずしも一致するわけではなく、APIの扱い辛さを感じています。(その他にもマスク内の選択が不可だったりと一括アウトライン化はまだまだ遠そうです)
参考:https://forums.adobexdplatform.com/t/help-no-way-to-do-group-within-group/876/
今回作成したファイルを一式アップロードしましたので、興味のある方はぜひ覗いてみて下さい。
以上です。