Figmaプラグインで時間のかかる処理をする場合は、メインコードから完了件数をUIへ送り、HTMLのprogress要素で表示できます。進捗の表示と処理の分割を組み合わせ、利用者が状態を確認できるようにします。
この記事の処理は、100件を50msずつ待つ説明用のダミーです。CSV出力やノード走査そのものではありません。開始の多重実行を防ぎ、中止の要求と完了を分ける最小構成を示します。
メインコードとUIの通信
- UIがstart-taskを送ります。
- メイン側が1件を終えるたびにcompletedとtotalを送ります。
- UIがprogressのvalueとmaxを更新します。
- 中止要求はcancelを送り、メイン側のフラグで次の反復を止めます。
- 完了・中止・失敗の結果を表示し、開始を再び使える状態にします。
awaitで制御を返さず長い同期処理を続けると、中止やUI更新をすぐ処理できない場合があります。実処理へ置き換えるときは、処理自体も分割します。出典:プラグインの実行環境、showUIとui。
ファイルを作成する
FigmaのNew pluginで生成したmanifestを基に、次の設定を使います。掲載例ではidを省略しています。自分のmanifestに割り当てられたidがある場合はその値を保ち、サンプルの任意文字列に置き換えません。3ファイルを同じフォルダへ保存してください。
manifest.json
{
"name": "進捗表示サンプル",
"api": "1.0.0",
"main": "code.js",
"ui": "ui.html",
"editorType": [
"figma"
],
"documentAccess": "dynamic-page",
"networkAccess": {
"allowedDomains": [
"none"
]
}
}code.js
figma.showUI(__html__, { width: 400, height: 200 });
let running = false, cancelled = false;
figma.ui.onmessage = async msg => {
if (!msg) return;
if (msg.type === "cancel") { cancelled = true; return; }
if (msg.type !== "start-task" || running) return;
running = true; cancelled = false;
try {
const total = 100;
for (let completed = 0; completed < total; completed++) {
if (cancelled) break;
await new Promise(resolve => setTimeout(resolve, 50)); // 説明用のダミー処理
figma.ui.postMessage({ type: "progress", completed: completed+1, total });
}
figma.ui.postMessage({ type: "done", cancelled });
} catch (_) { figma.ui.postMessage({ type: "error", message: "処理に失敗しました。" }); }
finally { running = false; }
};ui.html
<!doctype html><html lang="ja"><meta charset="utf-8">
<button id="start">開始</button><button id="cancel" disabled>中止</button>
<progress id="progress" max="100" value="0"></progress><p id="status">待機中</p>
<script>
const start = document.getElementById("start"), cancel = document.getElementById("cancel");
const progress = document.getElementById("progress"), status = document.getElementById("status");
function send(type) { parent.postMessage({ pluginMessage: { type } }, "*"); }
start.onclick = () => { start.disabled = true; cancel.disabled = false; progress.value = 0; status.textContent = "処理中…"; send("start-task"); };
cancel.onclick = () => { cancel.disabled = true; status.textContent = "中止を要求…"; send("cancel"); };
window.onmessage = event => {
if (event.source !== parent) return;
const msg = event.data && event.data.pluginMessage;
if (!msg) return;
if (msg.type === "progress") {
progress.max = msg.total; progress.value = msg.completed;
status.textContent = `処理中 ${msg.completed}/${msg.total}`;
} else if (msg.type === "done" || msg.type === "error") {
start.disabled = false; cancel.disabled = true;
status.textContent = msg.type === "error" ? msg.message : msg.cancelled ? "中止しました" : "完了しました";
}
};
</script></html>ENOENT・画面が出ない場合の確認
| 確認項目 | 対処 |
|---|---|
| mainがcode.tsになっている | TypeScriptをビルドし、生成したcode.jsを指定する |
| uiのファイルがない | manifestからの相対パスとファイル名を照合する |
| JSONの末尾にカンマがある | 正しいJSONに直す |
| 進捗が表示されない | 送るtypeとUIが受けるtypeを照合し、長い同期処理を分割する |
| 100%になったが保存できない | 作成処理とダウンロード操作を分け、UIを先に閉じない |
この例のmainはJavaScriptなので、掲載コードをそのままcode.jsへ保存します。TypeScriptを使う場合も、mainは生成先のJavaScriptです。コードを小分けにする仕組みはコンポーネントCSV出力で説明しています。
元記事の操作例
以下は元記事で掲載していた開発時の画面です。今回のコード改稿後に撮影した検証画像ではありません。現行の表示や変更結果は、複製したファイルで確認してください。

検証範囲と関連ガイド
今回確認したのは掲載コードの構文と、該当する入力・例外処理のローカル模擬検証です。Figmaデスクトップアプリ上での動作確認とは区別しています。改稿後のコードと、元記事が参照していたGitHub上の版が一致するとは限りません。
開発環境と登録手順はFigmaプラグイン開発入門、公開はCommunityへの申請を参照してください。
