Figma 插件开发笔记:组件查找、实例化与问题排查
功能概述
插件每次点击复制一个名为 box 的组件到画布下方,并将组件内名为 txt 的文本节点填充随机中文文字(20 字以内)。
组件的名称区分大小写,这个得注意。
组件绑定
查找组件
// 方法1:从已有实例反查组件(推荐,避免跨页加载)
const existingInstance = figma.currentPage.findOne(
(n) => n.type === 'INSTANCE' && n.name === 'box'
);
if (existingInstance && existingInstance.type === 'INSTANCE') {
component = await existingInstance.getMainComponentAsync();
}
// 方法2:遍历所有页面直接查找组件
await figma.loadAllPagesAsync();
for (const page of figma.root.children) {
const found = page.findOne(
(n) => n.type === 'COMPONENT' && n.name === 'box'
);
if (found && found.type === 'COMPONENT') {
component = found;
break;
}
}
创建实例并操作子节点
// 创建组件实例
const instance = component.createInstance();
figma.currentPage.appendChild(instance);
// 查找实例内的子文本节点
const txtNode = instance.findOne(
(n) => n.name === 'txt' && n.type === 'TEXT'
) as TextNode | null;
// 修改文本前必须先加载字体
if (txtNode) {
await figma.loadFontAsync(txtNode.fontName as FontName);
txtNode.characters = '新的文字内容';
}
定位到已有实例下方
const allInstances = figma.currentPage.findAll(
(n) => n.type === 'INSTANCE' && n.name === 'box'
);
const others = allInstances.filter(n => n.id !== instance.id);
if (others.length > 0) {
let bottom = others[0];
for (const inst of others) {
if (inst.y + inst.height > bottom.y + bottom.height) {
bottom = inst;
}
}
instance.x = bottom.x;
instance.y = bottom.y + bottom.height + 20; // 20px 间距
} else {
// 首个实例放在视口中心
instance.x = figma.viewport.center.x - instance.width / 2;
instance.y = figma.viewport.center.y - instance.height / 2;
}
踩坑记录
问题 1:无 UI 插件卡在 "Running" 状态
现象:插件没有调用 figma.showUI(),但 manifest.json 的 "ui" 字段已删除,插件一直显示 "Running..."。
原因:无 UI 插件(headless)在某些 Figma 版本中初始化不稳定,即使代码逻辑正确也可能卡住。
解决:改为有 UI 的架构 — 打开一个小弹窗,点击按钮触发操作。插件保持打开状态支持连续点击。
问题 2:findOne() 报错 "page has not been explicitly loaded"
现象:
Error: in findOne: Cannot access method 'findOne()' on a page
that has not been explicitly loaded.
原因:documentAccess: "dynamic-page" 模式下,非当前页面默认未加载,不能直接调用 findOne()。
解决:遍历页面之前先调用 await figma.loadAllPagesAsync()。
问题 3:mainComponent 属性被 dynamic-page 拦截
现象:
Error: in get_mainComponent: Cannot call with documentAccess: dynamic-page.
Use node.getMainComponentAsync instead.
原因:documentAccess: "dynamic-page" 禁止直接访问 InstanceNode.mainComponent 属性,必须使用异步方法。
解决:两处修改:
1. existingInstance.mainComponent → await existingInstance.getMainComponentAsync()
2. findAll 回调中无法使用异步方法 → 改为按实例名称过滤(name === 'box'),因为所有实例都继承组件名称
问题 4:点击按钮无任何反馈
原因:mainComponent 访问抛错但被安静吞掉,没有给到用户通知。
解决:在 UI 消息处理器外包裹 try-catch,捕获异常后通过 figma.notify() 给用户反馈。
figma.ui.onmessage = async (msg) => {
if (msg.type === 'copy') {
try {
await doCopy();
} catch (e: any) {
figma.notify('出错: ' + (e?.message || String(e)), { error: true });
}
}
};
架构
manifest.json → 声明 main: code.js, ui: ui.html
code.ts → code.js → 主线程,findOne + createInstance + 字体加载 + 定位
ui.html → iframe 弹窗,按钮 + 键盘快捷键
| 文件 | 职责 |
|---|---|
manifest.json |
插件注册,documentAccess: "dynamic-page" |
code.ts |
组件查找、实例化、文字填充、定位,消息监听 |
ui.html |
用户界面:复制按钮 + Ctrl+Enter 快捷键 |
前置条件
在 Figma 画布上需要准备:
- 一个组件,命名为
box - 组件内包含一个文本图层,命名为
txt
创建方式:选中一个 Frame/Group → 右键 → Create component → 在左侧面板双击名称改为 box,内部文本图层改名为 txt。