Novu use_figma 技能参考:Figma Plugin API 常用脚本模式详解(common-patterns)
【免费下载链接】novuThe open-source communication infrastructure for agents and products项目地址: https://gitcode.com/GitHub_Trending/no/novu
本文基于 Novu 仓库中.agents/skills/figma-use/references/common-patterns.md这份技能参考文档展开。它是use_figma技能(Figma Plugin API 自动化脚本执行)的"可直接复用的代码骨架库",覆盖脚本返回值结构、节点创建、自动布局、变量与模式(Modes)、组件变体、团队库导入以及大型组件集的多步拆分等 10 类高频操作。读完本文,你可以直接掌握在 Figma 文件上下文内编写、执行并串联多轮 Plugin API 脚本的完整套路,知道每种操作的正确代码形态、关键约束和常见陷阱。
文档定位:它是 use_figma 技能的"工作代码示例库"
common-patterns.md是 use_figma 技能 参考文档集的一部分,在 SKILL.md 的参考文档索引中被定义为:
Need working code examples — Script scaffolds: shapes, text, auto-layout, variables, components, multi-step workflows
也就是说,它不是 API 字典(那是 plugin-api-standalone.d.ts 的职责),而是一组已经过验证、可直接复制修改的最小工作示例,每个示例解决一类高频场景:
| 章节(原文档) | 解决的问题 |
|---|---|
| Basic Script Structure | 脚本如何把数据传回调用方 |
| Create a Styled Shape / Text Node / Frame with Auto-Layout | 基础节点的创建与定位 |
| Create Variable Collections and Bindings | 创建多模式变量集并绑定到填充 |
| Create Components and Import by Key | 组件变体属性、团队库组件导入 |
| Component Sets with Variable Modes | 变体命名与变量模式联合使用 |
| Multi-Step Large ComponentSet Pattern | 50+ 变体的分多次调用拆分策略 |
| Read Existing Nodes and Return Data | 只读巡检脚本的写法 |
在 Novu 仓库中,该技能由 skills-lock.json 登记为来自figma/mcp-server-guide的同步技能(figma-use),说明其内容遵循 Figma 官方 MCP 工具链的约定。技能规则明确要求:每次调用use_figma工具前必须先加载该技能,并始终传skillNames: "figma-use"参数用于使用追踪(该参数不影响执行)。
理解这些示例前,先记住三条贯穿所有代码的硬规则(来自 SKILL.md 的 Critical Rules,也与本文所有示例的形态直接对应):
return是唯一输出通道。返回值被自动 JSON 序列化;console.log()的输出永远不会传回调用方,不要调用figma.closePlugin(),也不要自己包 async IIFE(代码会自动包在 async 上下文中,顶层await可直接用)。- 失败是原子的。脚本一旦报错就完全不执行、文件零改动,所以正确姿势是先读懂报错、修正脚本再重试,而不是盲目重试。
- 小步增量推进。每次调用最多做 10 个左右逻辑操作,创建/修改后把节点 ID 返回,作为下一次调用的输入——这正是下面"Basic Script Structure"存在的意义。
基本脚本结构:return 结构化的 ID 追踪
原文档给出的第一例是所有脚本的骨架:
const createdNodeIds = [] const mutatedNodeIds = [] // Your code here — track every node you create or mutate // createdNodeIds.push(newNode.id) // mutatedNodeIds.push(existingNode.id) return { success: true, createdNodeIds, mutatedNodeIds, // Plus any other useful data for subsequent calls count: createdNodeIds.length }要点拆解:
- 每个脚本维护两个数组:
createdNodeIds(本次新建的节点)与mutatedNodeIds(本次修改的既有节点)。这是硬性要求而非建议——后续调用要靠这些 ID 去引用、校验和清理这些节点。 - 返回值可以附带任意"对后续调用有用"的数据,如数量统计、集合 ID、模式 ID 等。
- 与 gotchas.md 中的反例对照:只返回父级 frame 的 ID、丢失子节点 ID 是典型错误;正确做法是把 frame、rect、text 三个 ID 都放进
createdNodeIds,并额外给出rootNodeId方便定位。
基础节点创建:先找"空地",再放节点
创建带样式的形状
所有"追加到当前页面"的示例都复用同一段空位扫描逻辑:
// Find clear space to the right of existing content const page = figma.currentPage let maxX = 0 for (const child of page.children) { maxX = Math.max(maxX, child.x + child.width) } const rect = figma.createRectangle() rect.name = "Blue Box" rect.resize(200, 100) rect.fills = [{ type: 'SOLID', color: { r: 0.047, g: 0.549, b: 0.914 } }] rect.cornerRadius = 8 rect.x = maxX + 100 // offset from existing content rect.y = 0 figma.currentPage.appendChild(rect) return { nodeId: rect.id }这段代码体现了几条关键约定:
- 新顶层节点默认落在 (0,0)。直接
appendChild到页面会让多个新节点互相堆叠、并盖住既有内容。因此先遍历figma.currentPage.children,用Math.max(child.x + child.width)求出最右边界,再以maxX + 100的间距放置。注意这只对"直接挂在页面上的顶层节点"必要;放进 frame 或自动布局容器的子节点由父级排布,无需扫描。 - 颜色是 0–1 浮点域而非 0–255,例如
{r: 0.047, g: 0.549, b: 0.914}表示一种蓝色;写成{r: 12, g: 140, b: 233}会直接触发校验报错。 - fills 是整体重赋值,不是原地修改——示例中
rect.fills = [...]一次性替换整个数组。 - 创建后返回
{ nodeId: rect.id },供后续调用引用。
创建文本节点
// Find clear space to the right of existing content const page = figma.currentPage let maxX = 0 for (const child of page.children) { maxX = Math.max(maxX, child.x + child.width) } await figma.loadFontAsync({ family: "Inter", style: "Regular" }) const text = figma.createText() text.characters = "Hello World" text.fontSize = 16 text.fills = [{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }] text.textAutoResize = 'WIDTH_AND_HEIGHT' text.x = maxX + 100 text.y = 0 figma.currentPage.appendChild(text) return { nodeId: text.id }文本节点比形状多一条前置约束:figma.loadFontAsync()必须先于任何文本操作。SKILL.md 的规则 8 强调,这不仅是"设置文字"之前,而是包括appendChild、insertChild、setBoundVariable、setExplicitVariableModeForCollection在内的任何触碰含未加载字体节点的操作之前。如果文档中已存在文本节点,建议在脚本开头用await figma.listAvailableFontsAsync()发现可用字体后预加载全部字体,完整的预加载模式见 gotchas.md。另外注意字体样式名必须与实际发布名一致,"SemiBold"与"Semi Bold"的差异是经典踩坑点。
本例还展示了textAutoResize = 'WIDTH_AND_HEIGHT'(宽高都随内容自适应)的用法:文本节点不需要手动resize。
创建带自动布局的 Frame
// Find clear space to the right of existing content const page = figma.currentPage let maxX = 0 for (const child of page.children) { maxX = Math.max(maxX, child.x + child.width) } const frame = figma.createAutoLayout('VERTICAL') frame.name = "Card" frame.primaryAxisAlignItems = 'MIN' frame.counterAxisAlignItems = 'MIN' frame.paddingLeft = 16 frame.paddingRight = 16 frame.paddingTop = 12 frame.paddingBottom = 12 frame.itemSpacing = 8 frame.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }] frame.cornerRadius = 8 frame.x = maxX + 100 frame.y = 0 figma.currentPage.appendChild(frame) return { nodeId: frame.id }这里的关键 API 是figma.createAutoLayout('VERTICAL')——它是figma.createFrame()的"一步到位"替代:创建出的 frame 已启用自动布局且两轴默认 HUG 内容。SKILL.md 明确建议"任何需要自动布局的容器都优先用createAutoLayout,不要手写layoutMode+primaryAxisSizingMode+layoutSizingX的多步配置",因为这既啰嗦又容易踩顺序坑。该 API 在类型定义文件中也有正式声明,见 plugin-api-standalone.d.ts#L1083-L1093。
属性语义速读:
primaryAxisAlignItems = 'MIN':主轴(垂直方向)顶部对齐;counterAxisAlignItems = 'MIN':交叉轴(水平方向)左侧对齐;paddingLeft/Right/Top/Bottom = 16/16/12/12与itemSpacing = 8:内边距与子项间距,单位是 px 数值;- 创建后子节点可以直接设
layoutSizingHorizontal = 'FILL'撑满,前提是appendChild已完成(FILL必须在挂到自动布局父级之后设置,提前设置会抛错)。
变量系统:多模式集合与填充绑定
创建带多个模式的变量集合
const collection = figma.variables.createVariableCollection("Theme/Colors") // Rename the default mode collection.renameMode(collection.modes[0].modeId, "Light") const darkModeId = collection.addMode("Dark") const lightModeId = collection.modes[0].modeId const bgVar = figma.variables.createVariable("bg", collection, "COLOR") bgVar.setValueForMode(lightModeId, { r: 1, g: 1, b: 1, a: 1 }) bgVar.setValueForMode(darkModeId, { r: 0.1, g: 0.1, b: 0.1, a: 1 }) const textVar = figma.variables.createVariable("text", collection, "COLOR") textVar.setValueForMode(lightModeId, { r: 0, g: 0, b: 0, a: 1 }) textVar.setValueForMode(darkModeId, { r: 1, g: 1, b: 1, a: 1 }) return { collectionId: collection.id, lightModeId, darkModeId, bgVarId: bgVar.id, textVarId: textVar.id }流程解析:
createVariableCollection("Theme/Colors")创建集合,名称用斜杠表达层级;- 新集合自带一个默认模式,用
collection.renameMode(collection.modes[0].modeId, "Light")重命名为 Light,再collection.addMode("Dark")追加 Dark 模式; createVariable("bg", collection, "COLOR")创建颜色变量(第三个参数是变量类型,此处为COLOR),注意 SKILL.md 规则 11:createVariable的集合参数既可传对象也可传 ID 字符串,推荐传对象;- 每个模式用
setValueForMode(modeId, {r,g,b,a})独立赋值,实现 Light/Dark 双主题; - 把 collection、mode、variable 的 ID 全部 return——多步工作流的后续调用要靠这些字符串字面量取回对象。
补充一点 SKILL.md 规则 16 的要求:创建变量时应显式设置variable.scopes(如背景用["FRAME_FILL", "SHAPE_FILL"]、文字色用["TEXT_FILL"]、间距用["GAP"]),因为默认的ALL_SCOPES会污染所有属性选择器。完整 scope 清单见 variable-patterns.md。
把颜色变量绑定到填充
const variable = await figma.variables.getVariableByIdAsync("VariableID:1:2") const rect = figma.createRectangle() const basePaint = { type: 'SOLID', color: { r: 0, g: 0, b: 0 } } // setBoundVariableForPaint returns a NEW paint — capture it! const boundPaint = figma.variables.setBoundVariableForPaint(basePaint, "color", variable) rect.fills = [boundPaint] return { nodeId: rect.id }这个示例浓缩了两条最容易被忽视的规则:
- 跨调用取变量:上一轮 return 出的
bgVarId以字符串字面量(如"VariableID:1:2")传入本轮,用await figma.variables.getVariableByIdAsync(id)恢复为对象。 setBoundVariableForPaint返回的是一个全新的 paint 对象,必须捕获返回值再赋给rect.fills。它不会修改basePaint本身。这是 SKILL.md 规则 10 的原文:"returns a NEW paint — must capture and reassign"。该 API 在类型定义中的声明位置见 plugin-api-standalone.d.ts#L2157。
basePaint里的具体颜色值在这里只是占位——绑定后实际渲染颜色以变量值为准,所以示例统一写成黑色基座。
组件与变体:属性、导入与变量模式
带组件属性的变体创建
原文档在此节开头给出了一条总规则(加粗强调):组件属性(TEXT、BOOLEAN、INSTANCE_SWAP)必须在每个变体的循环体内、combineAsVariants之前添加,组件集(combine 后的 COMPONENT_SET)会从子组件继承这些属性。完整示例:
await figma.loadFontAsync({ family: "Inter", style: "Regular" }) // Assume defaultIconComp is an existing icon component (discovered earlier) const defaultIconComp = figma.getNodeById('ICON_COMPONENT_ID') const components = [] const variants = ["primary", "secondary"] for (const variant of variants) { const comp = figma.createComponent() comp.name = `variant=${variant}` comp.layoutMode = 'HORIZONTAL' comp.primaryAxisAlignItems = 'CENTER' comp.counterAxisAlignItems = 'CENTER' comp.paddingLeft = 12 comp.paddingRight = 12 comp.paddingTop = 8 comp.paddingBottom = 8 comp.layoutSizingHorizontal = 'HUG' comp.layoutSizingVertical = 'HUG' comp.cornerRadius = 6 comp.itemSpacing = 8 // TEXT property — label const labelKey = comp.addComponentProperty('Label', 'TEXT', 'Button') const label = figma.createText() label.characters = "Button" label.fontSize = 14 comp.appendChild(label) label.componentPropertyReferences = { characters: labelKey } // BOOLEAN + INSTANCE_SWAP — icon slot const showIconKey = comp.addComponentProperty('Show Icon', 'BOOLEAN', false) const iconSlotKey = comp.addComponentProperty('Icon', 'INSTANCE_SWAP', defaultIconComp.id) const iconInstance = defaultIconComp.createInstance() comp.insertChild(0, iconInstance) // icon before label iconInstance.componentPropertyReferences = { visible: showIconKey, mainComponent: iconSlotKey } components.push(comp) } const componentSet = figma.combineAsVariants(components, figma.currentPage) componentSet.name = "Button" // Layout variants in a row after combining (they stack at 0,0 by default) const colW = 140 componentSet.children.forEach((child, i) => { child.x = i * colW child.y = 0 }) // Resize from actual child bounds — formula-based sizing is error-prone let maxX = 0, maxY = 0 for (const c of componentSet.children) { maxX = Math.max(maxX, c.x + c.width) maxY = Math.max(maxY, c.y + c.height) } componentSet.resizeWithoutConstraints(maxX + 40, maxY + 40) return { componentSetId: componentSet.id, componentIds: components.map(c => c.id) }逐点解析:
- 命名约定
variant=${variant}:变体名即属性声明,primary/secondary会构成一个名为variant的属性轴。组件集排版代码正是靠解析这种key=value, key=value命名来定位网格坐标的。 - TEXT 属性:
comp.addComponentProperty('Label', 'TEXT', 'Button')返回值就是属性 key 字符串(形如"label#4:0",后缀不可预测),直接用于label.componentPropertyReferences = { characters: labelKey }把文本节点的characters关联到该属性。gotchas.md 专门用 WRONG/CORRECT 示例警告:不要猜测 key、不要把返回值当对象取Object.keys()(那会得到字符串首字符索引'0')。 - BOOLEAN + INSTANCE_SWAP 组合:
'Show Icon'是布尔开关,'Icon'是实例交换槽位。iconInstance.componentPropertyReferences同时挂visible: showIconKey(控制可见性)和mainComponent: iconSlotKey(控制可替换的主组件),这是"图标槽位 + 显隐开关"的标准写法。comp.insertChild(0, iconInstance)保证图标排在标签之前。 combineAsVariants(components, figma.currentPage)把独立组件合并为 COMPONENT_SET(类型定义见 plugin-api-standalone.d.ts#L1742-L1749),之后componentSet.name = "Button"重命名组件集。- 合并后变体全部堆在 (0,0):必须手动按
i * colW排开;并用resizeWithoutConstraints依据实际子节点包围盒外扩 40px 重设尺寸——原文档特意注明"formula-based sizing is error-prone"(用公式推算组件集尺寸容易出错,应以真实子节点边界为准)。
按 Key 导入团队库组件
// Import a single published component by key const comp = await figma.importComponentByKeyAsync("COMPONENT_KEY") const instance = comp.createInstance() instance.x = 40 instance.y = 40 figma.currentPage.appendChild(instance) // Import a published component set by key and select a variant const compSet = await figma.importComponentSetByKeyAsync("COMPONENT_SET_KEY") const variant = compSet.children.find((c) => c.type === "COMPONENT" && c.name.includes("size=md") ) || compSet.defaultVariant const variantInstance = variant.createInstance() variantInstance.x = 240 variantInstance.y = 40 figma.currentPage.appendChild(variantInstance) return { componentId: comp.id, componentSetId: compSet.id, placedInstanceIds: [instance.id, variantInstance.id] }原文档在此节明确划了一条边界:importComponentByKeyAsync与importComponentSetByKeyAsync导入的是团队库(team libraries)中已发布的组件,而不是当前文件里的组件;当前文件内的组件应直接用figma.getNodeByIdAsync()或findOne()/findAll()定位。变体选择用"按size=md命名查找 +compSet.defaultVariant兜底"的双保险写法,再对选中的变体调用createInstance()。返回值同时登记组件 ID 与实例 ID(placedInstanceIds),符合"返回所有创建/修改节点 ID"的硬规则。
组件集 + 变量模式的完整模式
这个示例把"变体轴"与"变量模式"对齐:primary/secondary 两个变体分别锁定变量集合中对应的模式。
await figma.loadFontAsync({ family: "Inter", style: "Medium" }) // 1. Create color collection with modes per variant const colors = figma.variables.createVariableCollection("Component/Colors") colors.renameMode(colors.modes[0].modeId, "primary") const primaryMode = colors.modes[0].modeId const secondaryMode = colors.addMode("secondary") const bgVar = figma.variables.createVariable("bg", colors, "COLOR") bgVar.setValueForMode(primaryMode, { r: 0, g: 0.4, b: 0.9, a: 1 }) bgVar.setValueForMode(secondaryMode, { r: 0, g: 0, b: 0, a: 0 }) const textVar = figma.variables.createVariable("text-color", colors, "COLOR") textVar.setValueForMode(primaryMode, { r: 1, g: 1, b: 1, a: 1 }) textVar.setValueForMode(secondaryMode, { r: 0.1, g: 0.1, b: 0.1, a: 1 }) // 2. Create components with variable bindings const modeMap = { primary: primaryMode, secondary: secondaryMode } const components = [] for (const [variantName, modeId] of Object.entries(modeMap)) { const comp = figma.createComponent() comp.name = "variant=" + variantName comp.layoutMode = "HORIZONTAL" comp.primaryAxisAlignItems = "CENTER" comp.counterAxisAlignItems = "CENTER" comp.paddingLeft = 12; comp.paddingRight = 12 comp.layoutSizingHorizontal = "HUG" comp.layoutSizingVertical = "HUG" comp.cornerRadius = 6 // Bind background fill to variable const bgPaint = figma.variables.setBoundVariableForPaint( { type: "SOLID", color: { r: 0, g: 0, b: 0 } }, "color", bgVar ) comp.fills = [bgPaint] // Add text with bound color const label = figma.createText() label.fontName = { family: "Inter", style: "Medium" } label.characters = "Button" label.fontSize = 14 const textPaint = figma.variables.setBoundVariableForPaint( { type: "SOLID", color: { r: 0, g: 0, b: 0 } }, "color", textVar ) label.fills = [textPaint] comp.appendChild(label) // 3. CRITICAL: Set explicit mode so this variant renders correctly comp.setExplicitVariableModeForCollection(colors, modeId) components.push(comp) } // 4. Combine into component set const componentSet = figma.combineAsVariants(components, figma.currentPage) componentSet.name = "Button" return { componentSetId: componentSet.id, colorCollectionId: colors.id }四步结构与关键细节:
- 变量集合的模式与变体一一对应:
primary模式背景为蓝色{r:0, g:0.4, b:0.9}、文字白色;secondary模式背景透明{r:0, g:0, b:0, a:0}、文字深灰。注意a字段在变量值层面是允许的(与纯色 paint 的color不带a、透明度放在 paint 层的opacity上不同)。 - 每个组件内完成绑定:背景
comp.fills和文字label.fills都用setBoundVariableForPaint生成新 paint 后整体赋值,前文"捕获返回值"规则再次出现。文本节点在设置characters/fontSize前先label.fontName = { family: "Inter", style: "Medium" },与脚本开头的loadFontAsync呼应。 - 最关键的一步
comp.setExplicitVariableModeForCollection(colors, modeId):原文档以 "CRITICAL" 标注——不显式锁定模式,该变体渲染时可能取到错误模式下的变量值,导致 primary 变体显示出 secondary 的配色。 - 合并与命名:
combineAsVariants+componentSet.name = "Button",return 组件集 ID 和颜色集合 ID 两条线索,供下一轮调用(如排版、截图验证)使用。
大型组件集:多步拆分模式(Multi-Step)
对于 50+ 变体的组件集,原文档给出策略:拆成多次use_figma调用,每次只做一件事,用 return 的 ID 作为下一轮的输入字面量。这直接呼应 SKILL.md 中"Incremental Workflow"一节"每次调用最多 10 个逻辑操作、每步验证后再前进"的原则。
第 1 次调用:建变量集合,返回全部 ID
// Hex-to-0-1 helper const hex = (h) => { if (!h) return { r: 0, g: 0, b: 0, a: 0 }; // transparent return { r: parseInt(h.slice(1,3), 16) / 255, g: parseInt(h.slice(3,5), 16) / 255, b: parseInt(h.slice(5,7), 16) / 255, a: 1 }; }; const coll = figma.variables.createVariableCollection("MyComponent/Colors"); coll.renameMode(coll.modes[0].modeId, "mode1"); const mode2Id = coll.addMode("mode2"); // Create variables from data map const colorData = { "bg/default": ["#0B6BCB", "#636B74"], /* ... */ }; const modeOrder = ["mode1", "mode2"]; const modeIds = { mode1: coll.modes[0].modeId, mode2: mode2Id }; const varIds = {}; for (const [name, values] of Object.entries(colorData)) { const v = figma.variables.createVariable(name, coll, "COLOR"); values.forEach((hex_val, i) => { v.setValueForMode(modeIds[modeOrder[i]], hex_val ? hex(hex_val) : { r:0, g:0, b:0, a:0 }); }); varIds[name] = v.id; } // Return ALL IDs — needed by subsequent calls return { collId: coll.id, modeIds, varIds };工程化要点:
hex辅助函数:把#RRGGBB十六进制拆成三段parseInt(x, 16) / 255归一到 0–1;空值返回全透明{r:0, g:0, b:0, a:0},让数据表可以直接表达"该模式无此颜色"。- 数据驱动:颜色定义集中在
colorData映射(键如"bg/default"编码了"用途/状态"),循环统一创建变量并逐模式赋值,避免为每个变量手写一段setValueForMode。 - return
{ collId, modeIds, varIds }:集合 ID、模式 ID 表、变量 ID 表全部交回,这是第 2 次调用的全部依赖。
第 2 次调用:用存储的 ID 建组件、合并、排版
await figma.loadFontAsync({ family: "Inter", style: "Semi Bold" }); // Paste IDs from Call 1 as literals const collId = "VariableCollectionId:X:Y"; const modeIds = { mode1: "X:0", mode2: "X:1" }; const varIds = { /* ... from Call 1 ... */ }; const getVar = async (id) => await figma.variables.getVariableByIdAsync(id); const bindColor = async (varId) => figma.variables.setBoundVariableForPaint( { type: 'SOLID', color: { r: 0, g: 0, b: 0 } }, 'color', await getVar(varId) ); const collection = await figma.variables.getVariableCollectionByIdAsync(collId); const components = []; for (const mode of ["mode1", "mode2"]) { for (const state of ["default", "hover"]) { const comp = figma.createComponent(); comp.name = `mode=${mode}, state=${state}`; comp.layoutMode = 'HORIZONTAL'; comp.primaryAxisAlignItems = 'CENTER'; comp.counterAxisAlignItems = 'CENTER'; comp.layoutSizingHorizontal = 'HUG'; comp.layoutSizingVertical = 'HUG'; comp.fills = [await bindColor(varIds[`bg/${state}`])]; comp.setExplicitVariableModeForCollection(collection, modeIds[mode]); // ... add text children ... components.push(comp); } } // Combine — all children stack at (0,0)! const cs = figma.combineAsVariants(components, figma.currentPage); cs.name = "MyComponent"; // CRITICAL: layout variants in a structured grid mapped to variant axes. const stateOrder = ["default", "hover"]; const modeOrder2 = ["mode1", "mode2"]; const colW = 140, rowH = 56; for (const child of cs.children) { const props = Object.fromEntries( child.name.split(', ').map(p => p.split('=')) ); const col = stateOrder.indexOf(props.state); const row = modeOrder2.indexOf(props.mode); child.x = col * colW; child.y = row * rowH; } // Resize from actual child bounds let maxX = 0, maxY = 0; for (const child of cs.children) { maxX = Math.max(maxX, child.x + child.width); maxY = Math.max(maxY, child.y + child.height); } cs.resizeWithoutConstraints(maxX + 40, maxY + 40); // Wrap in section const section = figma.createSection(); section.name = "MyComponent Section"; section.appendChild(cs); section.resize(cs.width + 200, cs.height + 200); return { csId: cs.id, count: components.length };这段是全文档信息密度最高的示例,值得逐段对照:
- ID 以字符串字面量粘贴:注释 "Paste IDs from Call 1 as literals" 点破了多步调用的数据传递方式——跨调用没有共享变量,第 1 次 return 的 JSON 由调用方原样嵌入第 2 次脚本。SKILL.md 的 Pre-Flight Checklist 中也有对应条目:"IDs from previous calls are passed as string literals (not variables)"。
- 两个小工具函数收拢样板:
getVar封装getVariableByIdAsync,bindColor把"取变量 + 生成绑定 paint"压缩成一步,comp.fills = [await bindColor(...)]即可。 - 双轴变体命名:
mode=${mode}, state=${state}声明了mode与state两个属性轴;后续排版代码用child.name.split(', ').map(p => p.split('='))+Object.fromEntries把名字反解析成属性表,再按stateOrder/modeOrder2查行列索引,把变体摆进"state 为列、mode 为行"的网格(child.x = col * colW; child.y = row * rowH)。这比单行排列更适合多变体集,也让网格对人类和后续脚本都可读。 - 再次出现的三条纪律:合并后子节点全在 (0,0) 需要手动排版;尺寸用
resizeWithoutConstraints从实际包围盒外扩得出;setExplicitVariableModeForCollection在每个组件上逐一调用。 - 用 Section 包裹:
figma.createSection()建分区并appendChild(cs)后按+200外扩 resize,给组件集留出画布余量,这是交付前的组织动作。
读取既有节点:只读巡检脚本
最后一个示例展示了"只读调用"——不创建任何节点,只把结构数据 return 回去:
const page = figma.currentPage const nodes = page.findAll(n => n.type === 'FRAME') const data = nodes.map(n => ({ id: n.id, name: n.name, width: n.width, height: n.height, childCount: n.children?.length || 0 })) return { frames: data }它的价值在于配合 SKILL.md 增量工作流的第 1 步Inspect first:写任何创建脚本前,先跑一轮只读脚本摸清文件里已有哪些 frame、命名习惯和层级,让新内容"匹配现状而不是强加新约定"。同一技能中还提供了列出全部页面/组件/变量集合的巡检脚本,可作为扩展模板。
模式速查:何时用哪段骨架
| 场景 | 核心 API | 对应章节 |
|---|---|---|
| 任何脚本收尾 | 结构化return { createdNodeIds, mutatedNodeIds, ... } | 基本脚本结构 |
| 新建顶层节点 | 空位扫描maxX + 100定位 | 形状/文本/Frame 示例 |
| 文本 | 先loadFontAsync,后createText | 文本节点 |
| 容器 | figma.createAutoLayout('VERTICAL') | 自动布局 Frame |
| 主题变量 | createVariableCollection+renameMode/addMode+setValueForMode | 变量集合 |
| 变量上色 | setBoundVariableForPaint返回值再赋fills | 填充绑定 |
| 变体属性 | 循环内addComponentProperty,再combineAsVariants | 组件变体 |
| 团队库复用 | importComponentByKeyAsync/importComponentSetByKeyAsync | 按 Key 导入 |
| 变体 × 模式 | 每组件setExplicitVariableModeForCollection | 变量模式组件集 |
| 50+ 变体 | 调用 1 建变量 return ID,调用 2 粘贴字面量建组件 | 多步模式 |
| 写前侦察 | page.findAll+return结构数据 | 读取节点 |
需要进一步深入时,建议按 SKILL.md 第 10 节的参考文档索引继续加载:plugin-api-patterns.md(fills、strokes、effects 等节点细节)、variable-patterns.md(scopes 与别名)、component-patterns.md(INSTANCE_SWAP 与变体排版)、text-style-patterns.md(字体发现与文字样式)、gotchas.md(全部已知坑位的 WRONG/CORRECT 对照),以及作为 API 权威来源的 plugin-api-standalone.d.ts(建议按符号 grep 而不是整文件加载)。
【免费下载链接】novuThe open-source communication infrastructure for agents and products项目地址: https://gitcode.com/GitHub_Trending/no/novu
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考