デザイナーでもやればできる！FigmaをJavaScriptで操作してみる

サイバーエージェントでコード書いたりデザインしたりしている谷（[@hiloki](https://twitter.com/hiloki)）です。
QiitaでのFigmaアドベントカレンダー6日目として書きます。

https://qiita.com/advent-calendar/2021/figma

## Figmaはプラグインがいっぱいある

Figmaの人気を支える一つとしては、プラグインの豊富さにあると思います。

https://www.figma.com/community/explore

デザイン作業の煩わしいところを解消してくれるものなど、業務効率を挙げるのに役立つものが多く存在しています。僕自身も、自分で少し面倒だなとおもった時、こういうのがあれば便利かもな、というのは[プラグインをつくって公開しています](https://www.figma.com/@hiloki)。

https://www.figma.com/@hiloki

Figmaのプラグインの内部はJavaScriptで実装することができ、Webフロントエンドの開発経験があれば、ある程度のことはさほど難しくないと思っています。とはいえ、普段コードを書く業務をやっていないと難しそうに見えるし、実際にプラグインとしての開発となると色々と越えないといけない壁はあるでしょう。

なのでこの記事では、Developer Toolsのconsoleをつかって、実際にJSでFigma上のオブジェクトそ操作する経験をしてみる、というのを書いていきます。
Qiitaだと多くの人がエンジニアだと思いますが、どちらかというと普段コードを書かない（まったく書いたことがないわけじゃない）人に向けた難易度で書いていきます。あんまりJavaScriptプログラミングっぽい説明なども極力避け、とにかく「難しくない」印象で進めていきます。

デザイナーじゃない方でも「ああ、こんな感じで操作できるのか」というのが分かって、デザイナーとの協業や自身の業務効率化のためにFigmaプラグインやAPIを使ったツール開発に入門するきっかけにでもなればと思います。

## FigmaでDeveloper Tools使えるの？

Developer Tools（デベロッパーツール）というと、Webの開発ではGoogle Chromeなどのブラウザにある開発者向けのツール群を指します。
Google Chromeでいえば、右クリックで「検証」を選ぶと出てくるパネル、あるいはショートカットで `Command + Option + I` で開くパネルです。
この記事で言及するDeveloper Toolsも、このGoogle ChromeのDeveloper Toolsと**まったく同じもの**です。

FigmaはWebブラウザで使えるのが特長ですが、そのためもちろんブラウザで利用するときにはそのブラウザのDeveloper Toolsが使えますし、FigmaのデスクトップアプリもChromiumというGoogle ChromeやMicrosoft Edgeのベースになっているもので、デスクトップアップでも実はDeveloper Toolsが開きます。

![FigmaでDeveloper Toolsを開いたキャプチャ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21225/e8f3d684-2e4a-4048-0f87-87971e779f30.png)

なので、この後に解説するコードを実行するときには、ブラウザでもデスクトップアプリでもどちらでも構いません。

### Developer Toolsでどうする？

Developer Toolsを開いたら「Console」を開きます。最初開くとよくわからないメッセージなどが出る可能性がありますが、それらは無視してください。

開いたら `>` という記号の箇所に文字が打ち込めるはずです。
試しにここに下記のコードを入力して実行してみましょう。

```
figma.notify("Hello Figma!");
```

すると、たまにFigmaを使ってて見かける通知メッセージが表示されませんか？

![Hello Figma!と書かれたFigmaの通知メッセージが表示されているキャプチャ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21225/5afe646d-c790-dab7-beb7-0e21a562ee6e.png)

この例では `"Hello Figma!"` という文字列を、`figma.notify` っていうあらかじめFigmaで用意されている方法（メソッド）に渡しているだけなので、この文字列を書き換えれば任意の内容で表示されます。

もうこれだけで、JavaScriptでFigmaの中で何かを起こすことができました。おしまい！

というのも寂しいので、もう少しチャレンジしてみましょう。

## 🙌 チャレンジ： `Frame XX` というデフォルトの名前のままになっているFrameを発見する

Figmaで色々とデザインを試行錯誤し、データを整理していくところでちゃんと名前をつけていく、という工程がある人は多いでしょう。
それをどこまで徹底するかというのは人・チームそれぞれではありますが、ここでは「ページ内のデフォルトのFrame名のままのレイヤーを見つけて撲滅する」という狙いで、「ページ上から`Frame XX` という名前のFrameを発見する」というのをやってみましょう。

（こうしたレイヤー名を任意の名前で検索するようなプラグインはすでにありますが、この記事では演習としてやってみましょう。）

考え方としては、次のような順番で進めていきます。

1. 現在のページを取得する
1. 対象範囲を決め、条件に当てはまるレイヤーを探す
1. 見つけたレイヤーを選択状態にする

今回はその対象となるFigmaファイルを用意したので、Figmaのアカウントでログインして開いてみてください。

[https://www.figma.com/file/rB1VE1a6cLAHe0ob9OiMlU/Figma-Manipuration-by-JavaScript?node-id=0%3A1](https://www.figma.com/file/rB1VE1a6cLAHe0ob9OiMlU/Figma-Manipuration-by-JavaScript?node-id=0%3A1)

編集はできませんが、閲覧することができるので、その権限の範囲でできることを今回やります。

### 1. 現在のページを取得する

今回やりたいことは「今開いているページ」を対象にします。なので、まずは「今見ているページ」という情報の取得からはじめます。

先程案内したFigmaのファイルを開き、Developer Toolsを開いて、次のコードをconsoleで実行してください。

```
const page = figma.currentPage;

console.log(page);
```

すると、おそらく次のような結果が帰ってくるはずです。

```
PageNode {id: '0:1'}
```

実際の画面だとこのキャプチャのような感じですね。

![前述のコードを実行したconsoleの結果画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21225/264a4617-fb8f-cff6-c538-442e4d7e16dc.png)

書かれているコードにも「`currentPage`（現在のページ）」とある通り、これを実行すると現在のページ情報を取得します。Figmaでは普段「レイヤー」と呼んでいるようなテキストやフレームの要素は、ScriptでアクセスするときにはNodeと呼ばれています。
実行結果にある `PageNode` というのは、ページそのものを指します。このNodeの話も真面目に解説していくとそれだけで一つの記事になってしまうので、ここでは単純に「現在のページ」という情報を取得できたということだけ考えておいてください。
ちなみにこの `currentPage` というのは、あくまでFigmaの世界の中でアクセスできる情報、もう少し正確にいえばFigmaで予め用意されているAPIで、 `figma` というオブジェクトからアクセスできます。
なので、Figmaの外（例えば[Googleのウェブページ](https://www.google.com/)）でDeveloper Toolsを開いて実行しても次のようなエラーが帰ってくるでしょう。

```
Uncaught ReferenceError: figma is not defined
```

他にJavaScriptとしての補足をすると、 `const` というのは変数の定義で、ここでは「`page` という変数に、 `figma.currentPage` の結果を入れる」ということをしています。そして、その結果をconsoleに出力する `console.log()` に渡して、consoleに結果を表示させています。
久しくコードを書くことから離れている人にとっての変数は `var` というものかもしれませんが、こちらも詳しくは解説しないので、ちゃんと理解したい人は調べてみてください。（例: https://techacademy.jp/magazine/14872）

### 2. 対象範囲を決め、条件に当てはまるレイヤーを探す

「現在のページ」がわかったので、今度はその中身にアクセスし、その中から `Frame XX` と名前のついたレイヤー（Node）を探しましょう。

`currentPage` のように、`findAll()`という便利な方法（メソッド）が用意されています。（[findAll · Figma Developers](https://www.figma.com/plugin-docs/api/properties/nodes-findall/#docsNav)）
とりあえず次のコードを実行してみましょう。

```
// 現在のページ
const page = figma.currentPage;

// 現在のページの中から「すべてのNode」を取得する
const nodes = page.findAll();

console.log(nodes);

// 結果
// (9) [InstanceNode, RectangleNode, TextNode, FrameNode, TextNode, FrameNode, TextNode, FrameNode, TextNode]
```

実行すると、現在のページにあるすべてのレイヤー（Node）が返ってきます。
`findAll()`もまたその英語の通りですが、入れ子になっている中身も含めてすべてを返してくれます。
今回は「`Frame XX` と名前のついたレイヤー（Node）」という条件に絞りたいのですが、その条件を `findAll()`に渡すと、その条件が当てはまるものだけを結果として返します。


```
const page = figma.currentPage;

// 現在のページの中から「名前に'Frame'を含むNode」を取得する
const nodes = page.findAll(node => node.name.includes('Frame'));

console.log(nodes);

// 結果
// (4) [FrameNode, TextNode, FrameNode, TextNode]
```

`findAll()`の引数（丸括弧の中）に条件がはいっています。このあたりはFigmaの話というよりはJavaScriptの話なので詳細は省きますが、とても噛み砕いていうと、

1. 現在のページの中から（`figma.currentPage`）
2. レイヤーの名前（`node.name`）の中に「Frame」という文字列が含まれているかどうか（`includes('Frame')`）
2. 条件にあった一覧を返す

ということをやっています。

さて、このサンプルのFigmaファイルの中で、デフォルトのフレーム名のままになっているのは「Frame 1」と「Frame 2」というレイヤーだけです。
ですが、前述の結果だと **4つ**の結果（`[FrameNode, TextNode, FrameNode, TextNode]`）が返ってきています。これは「I’m **Frame** 1」と「Me? **Frame** 2」という名前のテキストレイヤー（TextNode）が存在しているためです。
今回の目的は「デフォルトのフレーム名」を探したいので、条件をもう少し厳しくしないといけません...

...今回は「Frameという文字列からはじまる」というのを条件に加えます。
これも実際どのようにやるかを試してみましょう。

```
const page = figma.currentPage;

// 対象の条件を絞り込む正規表現
const regex = /^Frame/;
// 現在のページの中から「名前が'Frame'はじまるNode」を取得する
const nodes = page.findAll(node => regex.test(node.name));

console.log(nodes);

// 結果
// (2) [FrameNode, FrameNode]
```

これでようやく対象の2つに絞ることができました。不慣れな人にはちょっとグッと難易度があがったように見えるかもしれません。ここもやはりJavaScriptの話になるので、先程と同じように流れだけを理解してもらうと、

1. 現在のページの中から（`figma.currentPage`）
2. 正規表現（`/^Frame/` = 先頭から"Frame"という文字列ではじまる）に当てはまる（`regex.test`）レイヤー名（`node.name`）があるかどうか
2. 条件にあった一覧を返す

という感じです。一つ前の例と比べてのポイントは、 `'対象の文字列'.includes（'文字列')`だったものが、`正規表現.test('対象の文字列')`という書き方に変わっているところです。

### 3. 見つけたレイヤーを選択状態にする

ここまでで「Frameという文字列からはじまる名前をもつレイヤー」を絞り込むことができました。
ただここまでのやり方だけだと、consoleの中にただのプログラム上のデータ `[FrameNode, FrameNode]`が表示されているだけです。これだとFigmaの画面上では、どこにそのレイヤーがあるのかわかりません。

なので、最後はこの2つのレイヤーを「選択状態にする」ことでわかるようにしましょう。

```
const page = figma.currentPage;

// 対象の条件を絞り込む正規表現
const regex = /^Frame/;
// 現在のページの中から「名前が'Frame'はじまるNode」を取得する
const nodes = page.findAll(node => regex.test(node.name));

// 選択状態にしたレイヤー（Node）がちょうど画面に収まるようズームする
figma.viewport.scrollAndZoomIntoView(nodes);

// 対象のレイヤー（Node）を選択状態にする
figma.currentPage.selection = nodes;
```

実行すると、おそらくこのように対象レイヤーが選択状態になるはずです。

![JavaScriptによって選択されたレイヤーが表示されているFigmaの画面キャプチャ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21225/1735a525-e164-2556-e46c-0b26356670d5.png)

ここでのポイントは `figma.currentPage.selection` です。これも英語でみるとそのままです、「現在のページ」の「選択中のもの」。なので、これに条件を満たしたレイヤー情報を代入（`figma.currentPage.selection = nodes`）することによって、画面上で選択状態にしています。

`figma.viewport.scrollAndZoomIntoView(nodes)`はややおまけですが、選択したものをちょうどよく画面表示させるために追加してみました。これも英語で理解すると「ビューポート（画面）上」で「nodesが表示される範囲にズームとスクロール（移動）する」ということをしています。

### 4. さらにもう少しアレンジ

冒頭で画面上に「Hello Figma!」という文字列を表示する例を紹介しました。それを応用して「条件に該当したレイヤーの数が何個だったか」を最後に表示させてみましょう。
このコード例は記事の最後に記載するので、チャレンジになるようであればぜひ試してみてください。

「何個だったか」についてはJavaScriptで「配列を数える」という方法で考える・検索してみてください。

[コード例は記事の最後に👇](https://qiita.com/drafts/8f97c7705d0768c9462d/edit#4%E3%81%AE%E7%AD%94%E3%81%88)

## Scriptによる操作を便利にするプラグイン「Scripter」

今回は基本的な内容をレクチャーするためにconsoleを使いましたが、もし便利なコードをかけたとして、それを毎回consoleにコピペして実行するのは大変です。Developer Toolsだとそうしたコードをスニペットとして登録する機能はありますが、Figmaでは[Scripter](https://www.figma.com/community/plugin/757836922707087381/Scripter)というプラグインを使うのがおすすめです。

https://www.figma.com/community/plugin/757836922707087381/Scripter

Scripterプラグインを起動し、「＋」から新規登録、「実行ボタン（▶）」で実行する、というだけです。

![Scripterから新規登録と実の操作をした動画](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21225/beae5cfa-a98b-d336-f54a-d27771b34f22.gif)

Scripterには色々と便利なデフォルトのスニペットや関数があるようなので、興味があれば色々と触ってみると良いでしょう。

## まとめ

今回の記事では、あまりコードを書かないデザイナー向けに、かなり噛み砕いた内容なのでだいぶJavaScriptとしての説明を省いています。
とにかく「どういうことができるか」を知ってもらい、そこから色々コードをカスタマイズし、そこからJavaScriptやFigmaのプラグイン開発への関心をもってもらえたら嬉しいです。
Figmaの多彩で多機能なプラグインも、こうした小さなプログラムの組み合わせでできているので、ぜひプラグイン開発にもチャレンジしてください。

[Figma Plugin APIのドキュメント](https://www.figma.com/plugin-docs/intro/)を眺めてみると、もっと色々できそうなことが発見できるので、ぜひこちらも参照してみてください。

## すごく余談

最後に紹介したScripterはRasmus Anderssonというデザイナーの方です。
Spotify創業期メンバーで、Facebook、Dropbox、Figmaと渡り歩き、現在は違う会社にいます。
実績として、小さなサイズでも読みやすいオープンソースフォント[Inter](https://rsms.me/inter/)のデザイナーであり、GraphQL発足メンバーの一人でもあり...他にも今でもいろんな実績があって、ユニコーンすぎてやばいですね。

https://rsms.me/

## 4の答え

```
const page = figma.currentPage;

// 対象の条件を絞り込む正規表現
const regex = /^Frame/;
// 現在のページの中から「名前が'Frame'はじまるNode」を取得する
const nodes = page.findAll(node => regex.test(node.name));

// 選択状態にしたレイヤー（Node）がちょうど画面に収まるようズームする
figma.viewport.scrollAndZoomIntoView(nodes);

// 対象のレイヤー（Node）を選択状態にする
figma.currentPage.selection = nodes;

// lengthは「配列」の数をカウントするので、その数字と文字列を組み合わせる
figma.notify(nodes.length + "つ発見しました🎉");

//　下記のような書き方もできます。「テンプレートリテラル」で調べてみてください 
// figma.notify(`${nodes.length}つ発見しました🎉`);
```