こちらはFigma 開発 Advent Calendar 2022,23日目の記事です。
2022年12月に,キーオブジェクトに整列・分布するFigma/FigJam用プラグイン「Singari」を公開しました。すでにFigmaコミュニティの次のページからアクセス可能です。
Adobe Illustratorの同機能と似た,キャンバス上で追加選択したアイテムをキーオブジェクトとして扱う操作にこだわって開発しています。ぜひ使ってみてください。
使用方法など詳細は,別記事【解決】Figmaでもキーオブジェクトに整列・分布したい!にまとめました。興味があるかたはそちらをどうぞ。
今回の記事では開発者向けに,プラグインAPIでは不可能に見える 「キャンバス上で追加選択したアイテムをキーオブジェクトとして扱う操作」を実現した,そのしくみを解説します。
というのも,Figmaでは「こう考えて,こう開発しました」という内容の記事が公開されていて面白いのです。それに倣ってみようと思います。
- Behind the feature: find and replace
- Getting to the bottom of line height in Figma
- Realtime Editing of Ordered Sequences
目次
そもそもキーオブジェクトに整列って何?
Adobe Illustratorにある機能です。アートボード上の特定のアイテム(キーオブジェクト)を基準とし,別のアイテムを動かしてそれに整列させます。キーオブジェクト自体の位置はそのまま変わりません。
次のように操作します。
- 整列したいアイテムをすべてを選択する
- キーオブジェクトにしたいアイテムを追加でタップする(修飾キーなどは不要)
- 上揃えなど,揃えたい方向のボタンを押す
ダイアログや文字入力を介さず,画面上の見た目がそのまま動作に反映されるのが使いやすさのポイントです。
Figmaにはキーオブジェクトの概念は存在しない
Figma自体が非対応なので当然のことながら,プラグインAPIにはキーオブジェクトを取得する機能はありません。何らかの別の方法で,特別扱いにするアイテムをプログラムから見分ける必要があります。
例えばAdobe XDの場合,プラグインAPIで選択アイテム一覧を取得すると,一覧はユーザーが選択した順番で並んでいます。最初か最後のアイテムを特別扱いするとわかりやすそうです。
しかしFigmaの選択アイテム(selection)仕様を見ると,順番は不特定なのであてにしてはいけないと書いてあります。さてどうしましょう。
ユーザーが選択を変更したときイベントが発生する
figma.onというイベントを司る部分の資料を見て,選択変更時に割り込み処理を入れられるとわかりました。つまり,そのとき選択アイテムを記録すれば最後に選択したアイテムは特定できます。それで行きましょう。
選択アイテムを記録するコードは次のようになります。
1 2 3 4 5 6 7 |
// 前回の選択アイテム一覧を保持する変数 let LastSelection: ReadonlyArray<SceneNode> = [] ; // 選択変更時,その時点の選択アイテム一覧を変数LastSelectionに記録する figma.on('selectionchange', () => { LastSelection = figma.currentPage.selection ; }) ; |
キーオブジェクトは論理的に推察する
記録した選択アイテム一覧を解析して,キーオブジェクトがどれか割り出します。条件は次の通りです。
- 最後に選択した1つのアイテムをキーオブジェクトとみなす
- 前回の選択アイテムと今の選択アイテムを比べ,1つだけ増えたアイテムがあったらそれがキーオブジェクト
先程のコードを改造します。
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 28 29 30 |
// 前回の選択アイテム一覧を保持する変数 let LastSelection: ReadonlyArray<SceneNode> = [] ; // キーオブジェクトを保持する変数。これが今回の目的の値 let KeyObject: SceneNode|null = null ; // 選択変更時に毎回実行される処理 figma.on('selectionchange', () => { let tempLastSelectedItem: SceneNode|null = null ; // 今の選択を取得する const currentSelection: ReadonlyArray<SceneNode> = figma.currentPage.selection ; // 前回の選択が1つ以上あり,今の選択は2つかそれ以上ある場合のみ実行する if( (LastSelection.length > 0) && (currentSelection.length >= 2) ) { // 前回の選択時なかった(増えた)アイテムだけ抽出する const addedItems: ReadonlyArray<SceneNode> = currentSelection.filter((aItem) => { return !LastSelection.includes(aItem) ; }) ; // 増えたアイテムが1つのときのみ取得する if(addedItems.length === 1) {tempLastSelectedItem = addedItems[0] ;} } // 条件に合うアイテムがあればそれをキーオブジェクトとして,ない場合はnullとして目的の変数を更新する KeyObject = tempLastSelectedItem ; // 今の選択を保存する。次の実行時,前回の選択範囲となる LastSelection = currentSelection ; }) ; |
整列・分布は地道に座標を計算してセットするだけ
キーオブジェクトが判明したら,それと整列対象アイテムの座標を調べて合わせるだけです。
Frameの中と外の座標系を揃えるため絶対座標で比較します。絶対座標は例えば次のようなfunctionで取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/** * FigmaのabsoluteBoundingBox・absoluteRenderBoundsは,回転したnodeで数値がよく狂っている。absoluteTransformから直接取得する * @param targetNode - 対象のnode * @return 絶対Rect */ const getAbsoluteBounds = (targetNode: SceneNode): Rect => { const { width, height, absoluteTransform } = targetNode ; const res = { x: absoluteTransform[0][2], y: absoluteTransform[1][2], width, height, } ; return res ; } ; |
取得した座標から移動先を算出したあと,整列対象アイテムのxやyのプロパティにセットして実際に移動します。
1 2 |
// 例えば,整列対象アイテムtargetNodeのx座標を100にするコード targetNode.x = 100 ; |
まとめ
このように,サービスに直接存在しない機能でも,単純な処理の組み合わせで案外実装できたりします。プログラミングは面白いですね。
作者に感謝を伝えたい!
Buy me a coffeeは、クレジットカード払いなどでクリエイターにコーヒーをおごれるサービスです。ツール・情報が役に立った! 感謝の気持ちを表現したい! というかた、おごっていただけましたら嬉しいです☕️
このサイトで配布しているスクリプトやその他のファイルを,無断で転載・配布・販売することを禁じます。
それらの使用により生じたあらゆる損害について,私どもは責任を負いません。
スクリプトやファイルのダウンロードを行った時点で,上記の規定に同意したとみなします。