Figma 的节点树内容丰富但噪音较多。Frame、Group、Component、Instance、Text、Rectangle、Ellipse、Vector——每个都有数十个可选字段,其中一些相互冲突。直接使用原始 Figma API 响应工作的 Agent 会将认知预算花在理解 schema 上,而不是编写代码。
figmascope 将树规范化为四种节点类型:stack、overlay、absolute 和 leaf。每个 screens/*.json 文件中的每个节点都是这四种之一。没有其他。
四种类型
stack
stack 是具有 Figma 自动布局的节点——layoutMode: "VERTICAL" 或 layoutMode: "HORIZONTAL"。其子元素沿轴向流式定位。间距和内边距是明确的。
{
"kind": "stack",
"id": "123:456",
"name": "ContentColumn",
"axis": "vertical",
"gap": 16,
"paddingTop": 24,
"paddingBottom": 24,
"paddingLeft": 20,
"paddingRight": 20,
"primaryAxisAlignItems": "SPACE_BETWEEN",
"counterAxisAlignItems": "CENTER",
"width": 390,
"height": 844,
"fills": [{ "type": "SOLID", "color": "#1a1a2e" }],
"children": [ ... ]
}
在 Jetpack Compose 中映射为 Column(垂直轴)或 Row(水平轴)。primaryAxisAlignItems 和 counterAxisAlignItems 字段分别映射到 Arrangement 和 Alignment。gap 变为 Arrangement.spacedBy()。
overlay
overlay 是子元素在其内部具有绝对位置的节点。Figma 将其表示为 layoutMode: "NONE" 的 Frame,子元素有 absoluteBoundingBox 坐标。overlay 类型捕获这一点,而不需要 Agent 通过推理原始坐标来理解结构。
{
"kind": "overlay",
"id": "123:789",
"name": "CardOverlay",
"width": 358,
"height": 200,
"fills": [{ "type": "SOLID", "color": "#ffffff" }],
"children": [
{
"kind": "absolute",
"offset": { "x": 16, "y": 16 },
"child": { ... }
}
]
}
在 Compose 中映射为 Box。子元素使用 Modifier.offset() 或 Modifier.align() 定位,具体取决于它们的位置与父元素边界的关系。
absolute
absolute 节点是一个包装器,在其父 overlay 内的特定 (x, y) 偏移处携带单个子元素。它是一个薄薄的结构节点——它的存在是为了保留来自 Figma 的空间关系,而不将位置与内容混为一谈。
{
"kind": "absolute",
"offset": { "x": 24, "y": 140 },
"child": {
"kind": "leaf",
"name": "BadgeLabel",
"text": "NEW",
"stringRef": "badge.new",
...
}
}
在 Compose 中,这通常成为带有 Modifier.offset(x.dp, y.dp) 的 Box 的子元素。如果间距令牌在范围内存在,偏移值是令牌替换的候选。
leaf
leaf 是终端节点——没有子元素。它有样式(填充、描边、圆角半径)以及可选的文本内容。文本叶节点有 text 字段(字面字符串)和 stringRef 字段(来自 strings.json 的 i18n 资源键)。
{
"kind": "leaf",
"id": "123:101",
"name": "SpeedLabel",
"text": "Speed Test",
"stringRef": "speed.test",
"fontSize": 18,
"fontWeight": 600,
"fills": [{ "type": "SOLID", "color": "#ffffff" }],
"width": 160,
"height": 28
}
对于文本内容映射为 Compose 的 Text() composable,对于非文本叶节点根据填充类型映射为 Surface、Box 或 Image。
四种类型与任何 UI 框架的组合模型一一对应。Stack 是流式布局。Overlay 是 z 轴定位的容器。Absolute 携带空间元数据。Leaf 是内容。就是这样。Figma 节点分类有 12 种以上的节点类型——IR 将其减少为四种。
如何确定类型
分类逻辑:
- 如果
layoutMode是"VERTICAL"或"HORIZONTAL"→stack - 如果
layoutMode是"NONE"且节点有子元素 →overlay(子元素包装为absolute) - 如果节点是 overlay 的直接子元素且携带位置 →
absolute - 如果节点没有子元素且不是 absolute 包装器 →
leaf
当 layoutMode: "NONE" 的 Frame 的子元素有非平凡重叠边界框时,_meta.json 中的 layout-mode-none-inferred 警告会触发。figmascope 将其视为 overlay,但注明推断,因为实际上一些 layoutMode: "NONE" 的 Frame 只是单个子元素的容器(零重叠),可以简化。警告让 Agent 决定如何处理。
absoluteBoundingBox——为什么要保留它
IR 中的每个节点都保留其来自 Figma 的 absoluteBoundingBox,即使父元素是 stack(理论上绝对位置与布局无关):
{
"kind": "stack",
"absoluteBoundingBox": { "x": 0, "y": 88, "width": 390, "height": 756 },
...
}
这是为需要推断不共享直接父子关系的节点之间空间关系的 Agent 而存在的。将来自树的不同分支的两个元素重叠需要知道它们的绝对位置,而不仅仅是相对偏移。坐标系是 Figma 的画布——左上角原点,y 向下递增。
它还有助于 PNG 交叉参考。_meta.json 包含 pngCount 字段,导出的 PNG 按屏幕 slug 命名。如果你将 IR 与截图进行比较,absoluteBoundingBox 让你在图像中定位特定节点。要在自己的设计上查看这一点,从 figmascope.dev 导出包。
容器类型上的填充和描边
一个常见的混淆点:stack 和 overlay 节点可以有填充,不只是叶节点。有背景颜色的列仍然是 stack——它只是还有一个 fills 数组。figmascope 保留了这一点:
{
"kind": "stack",
"axis": "vertical",
"fills": [{ "type": "SOLID", "color": "#f6f2ea" }],
"cornerRadius": 12,
...
}
在 Compose 中,这通常成为 Surface 或 Card 内的 Column,填充颜色和圆角半径应用于容器。IR 不会将填充折叠到子元素中——它保留在 Figma 设置它的节点上。
容器类型上的渐变填充会发出 background-gradient-not-supported:<name> 警告。节点仍然以其他字段完整存在于 IR 中。Agent 应将填充视为待办事项,要么用纯色近似,要么按照 CONTEXT.md 范围说明生成自定义绘制调用。
stringRef + text 的关系
文本叶节点同时携带:
text:来自 Figma 的字面字符串值(例如"Speed Test")stringRef:来自strings.json的资源键(例如"speed.test")
如果文本节点的内容因碰撞或过滤(仅数字、为空、太短)而从 strings.json 中删除,叶节点仍然有 text 但 stringRef 将缺失。在那种情况下 Agent 应回退到字面值。有关完整的碰撞处理逻辑,请参阅 strings.json。
当两者都存在时,CONTEXT.md 约束说使用 stringRef。text 字段是为 Agent 推理而存在的(让它知道字符串说什么而无需打开 strings.json),也作为后备。
IR 中的组件实例
当 Figma 节点是组件的 INSTANCE 时,IR 节点携带两个额外字段:
{
"kind": "stack",
"componentId": "789:012",
"componentName": "PrimaryButton",
...
}
这链接回 components/inventory.json。Agent 知道这个节点是 PrimaryButton 的实例而非定制布局,应该引用现有组件而不是生成重复代码。完整覆盖在 组件库存中。
真实屏幕示例
一个简化但结构上准确的示例,包含标题、内容列和浮动按钮的屏幕:
{
"screen": "home",
"root": {
"kind": "overlay",
"name": "HomeScreen",
"width": 390,
"height": 844,
"fills": [{ "type": "SOLID", "color": "#0d0d1a" }],
"children": [
{
"kind": "absolute",
"offset": { "x": 0, "y": 0 },
"child": {
"kind": "stack",
"name": "ContentArea",
"axis": "vertical",
"gap": 24,
"paddingTop": 56,
"paddingLeft": 20,
"paddingRight": 20,
"children": [
{
"kind": "leaf",
"name": "Title",
"text": "Speed Test",
"stringRef": "speed.test",
"fontSize": 28,
"fontWeight": 700
}
]
}
},
{
"kind": "absolute",
"offset": { "x": 111, "y": 752 },
"child": {
"kind": "stack",
"name": "StartButton",
"componentId": "321:654",
"componentName": "PrimaryButton",
"axis": "horizontal",
"gap": 8,
"paddingTop": 16,
"paddingBottom": 16,
"paddingLeft": 32,
"paddingRight": 32,
"cornerRadius": 24,
"fills": [{ "type": "SOLID", "color": "#7f5cfe" }]
}
}
]
}
}
从这里 Agent 可以正确生成一个 Compose Box 屏幕,内容区域有绝对定位的 Column,底部有绝对定位的 PrimaryButton 组件。每个布局决策都可以从 IR 推导出来,无需猜测。
有关令牌如何应用于此结构中的间距和颜色值,请参阅 tokens.json 详解。有关使用此 IR 与 Cursor 或 Claude Code 的完整 Agent 工作流,请参阅 从 Figma 生成 Jetpack Compose。通过将你的 Figma URL 粘贴到 figmascope 来自己尝试。