ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

LLM自动分析UI图层生成切图清单的实践与避坑指南

2026/9/7 12:17:17 拓冰建站 浏览量
LLM自动分析UI图层生成切图清单的实践与避坑指南 干前端开发现这行的十有八九都经历过“切图”这道工序。设计师交付一版设计稿你需要把里面的图标、背景、按钮一个个切出来按分辨率导出还要对着标注图量间距、对色值、算圆角。工作本身不复杂但架不住量大、重复、还容易因为沟通偏差返工。所以当LLM的能力圈越来越大圈内开始有人拿它写代码、写测试、搭知识库的时候我心里冒出一个念头能不能让LLM直接对着UI图层做切图这个想法说白了就是跳过“人肉看图、手动切片”的过程让大模型去理解设计稿的图层结构自动给出切图清单和样式标注。这个实验我前前后后折腾了两周踩了不少坑也真的跑通了一条可行的路子这篇文章就是把整个实验过程和思路完整记录下来给想往这个方向试水的人留一份参考。我需要提醒一下这篇文章不是一个成熟工业级方案的教学而是一次“实验记录经验总结”。我下面会讲清楚我为什么选择LLM来分析图层JSON而不是直接让它读图会放完整的预处理脚本和提示词模板也会把那些最让人抓狂的坑一个个列出来。如果你平时也在做UI自动化测试、前端工程化或者组件库建设这篇文章里有一半内容可以直接迁移过去用。1. 为什么突然想用LLM做UI切图1.1 传统切图流程的痛点先说传统切图流程。大多数情况下设计稿都是Figma、Sketch或者PSD文件里的一堆图层设计师把图层命名好、分组分好前端拿过来要做的无非这几件事把需要的图层导出成PNG、SVG等格式记录每个元素的尺寸、坐标、颜色、字体、圆角、阴影把布局结构梳理成CSS或者小程序WXSS。听起来不难但问题恰恰出在“量”上——一个中等复杂度的页面可能有几十上百个图层登录页这种简单页面也有二十来个可导出元素。我参与过的项目里最耗时的往往不是写样式而是“核对”。设计师给的标注图老了要核对不同分辨率下要补切多套图要核对动效稿里要抠出关键帧要核对。团队里经常出现这样的情况一个视觉复杂的活动页光是切图和标注就要花掉一整个下午。而且这个活没有太多技术含量稍微熟练一点的初级开发或设计就能干但它就是得有人干。哪怕是在组件库已经比较完善的团队里遇到定制化页面、活动页、运营图仍然逃不掉这套重复劳动。1.2 这个实验到底想验证什么我启动这个实验之前给自己定了一个明确的验证目标LLM到底能不能从UI图层文件中提取出“切图所需的关键信息”并把这些信息转成可执行、可校验的导出清单。注意我的目标不是让LLM直接生成图片文件因为那本来就不是文本模型该干的活。我想验证的是链条的前半段从原始图层数据到结构化切图清单这一段能不能用LLM替代人工。这里还要回答一个问题为什么不用现成的设计规范扫描工具比如公司内部常有的设计稿标注平台、或者Figma自带的导出功能答案很简单这些工具能做“规则性工作”但做不了“语义性判断”。比如我想把页面上所有“主按钮”找出来按视觉层级排列并且给它们统一命名成btn-primary、btn-secondary传统工具做不到。它需要理解图层之间的层级关系理解“哪个元素是图标、哪个元素是文字背景”这种理解能力正好是LLM相对擅长的方向。所以我预测LLM在这里的价值是“语义理解结构化输出”而不是“像素级还原”。2. 实验的整体技术路线设计2.1 三条路线怎么选我都试过一遍既然要拿LLM做切图我先梳理了三条可能的技术路线排序不代表优先级只代表我推演时的自然思路。第一条路线直接喂图片。把设计稿截图给多模态模型让模型直接通过视觉看然后输出每个图层的位置和切图建议。这条路线试下来是最直观的但也是最不稳的。原因是多模态模型对于元素的位置只能输出一个大概的边界框像素级精度完全不够而且它很容易漏掉细小的图标、忽略叠在一起的层次。我测试下来一个20个图层左右的简单页面视觉方案最多只能稳定识别出60%的图层其余全靠猜。第二条路线解析设计稿源文件提取图层树JSON喂给文本模型。这条路线需要写一套解析和预处理逻辑但好处是图层数据是精确的坐标、大小、颜色、字体信息一个都不少模型要做的是在精确数据上做语义归纳而不是从零开始识别。这条路线可以确保输出结果在“数据准确性”上是可信的模型的“幻觉”只能出现在分类和命名层面而不会出现在坐标尺寸上。第三条路线用传统图像处理算法做图层自动切割再让LLM做命名和归纳。这种方案本质上是用OpenCV之类工具找元素边框LLM只是辅助。问题在于设计稿不是截图有很多隐藏图层、不可见图层、组件嵌套传统算法很难处理这些非视觉信息适用范围非常有限。三条路线各试了一轮之后我把重心放在第二条路线上解析设计稿图层树JSON配合文本型LLM做结构化输出最后用脚本自动生成切图。核心原因就是“精确数据交给程序语义理解交给模型”各干各擅长的事。2.2 我的技术栈和模型选择这个实验不是一个复杂工程但技术栈还是选择了我最顺手的组合Python做数据处理Figma API拿图层数据LLM接口用OpenAI兼容格式方便随时换模型最后输出结果用脚本二次校验。整体流程我会在后面的实操章节里展开这里先把工具选型逻辑说清楚。先说说为什么不直接用Figma的官方插件生态。Figma本身有很完善的插件API理论上可以通过插件直接导出每个图层资源。但插件方案有一个问题它是面向单机交互的没办法做成批量流水线。我需要的是“拿一批设计稿文件输出一批结构化切图清单”所以API方式更适合我。Figma API可以拿到完整的文件节点树每个节点的类型、名称、坐标、尺寸、填充色、透明度、圆角半径都在里面这正是我需要的原始材料。模型选择上我实测过GPT-4o系列、Claude的Sonnet系列以及本地部署的Qwen2.5。这里直接给结论能力强一点的商业模型和开源模型在“简单页面的图层归纳”上差别不大但在“复杂嵌套组件”的处理上商业模型明显更稳。我最终是基于OpenAI兼容格式封装了一层接口底层模型可以随时切换实际执行的时候主用Claude的Sonnet版本本地Qwen模型作为候选备用。还有一个取舍我想单独说为什么用文本JSON而不是转成markdown表格再喂给模型我的第一版就是转成markdown表格列是“名称、类型、x、y、宽、高”结果发现图层一多markdown表格会变得非常冗长而且层次的嵌套关系完全丢失。改成树形文本格式之后模型对嵌套结构的理解明显好了很多。格式转换这一步看起来不起眼实际对结果质量影响巨大。3. 核心细节解析把设计稿变成LLM能读懂的“台词”3.1 图层数据获取为什么选Figma API设计稿源文件有很多种形态PSD、Sketch、Figma、即时设计等。我这里用Figma做主要实验对象因为它的API在同类工具里最完善文档清晰、返回结构稳定。通过Figma的REST API你可以用文件key和节点id拿到整棵图层树每个节点包含如下核心字段id、name、typeFRAME、GROUP、RECTANGLE、TEXT、VECTOR等、absoluteBoundingBoxx、y、width、height、fills、strokes、effects、cornerRadius等。先放一个我实际调用过的简化版请求示例这里我假设你已经在Figma开发者后台创建了Personal Access Tokenimport requests FIGMA_TOKEN 你的token FILE_KEY 你的文件key headers {X-Figma-Token: FIGMA_TOKEN} url fhttps://api.figma.com/v1/files/{FILE_KEY} resp requests.get(url, headersheaders) data resp.json() # 拿到整棵节点树后可以按需裁剪节点注意一个问题整棵文件树可能非常大一个设计文件包含几十个页面、上百个Frame直接全量返回不仅耗时token计算也会爆炸。所以更稳妥的做法是先通过文件接口拿到页面列表锁定目标页面的node id再用GET /v1/files/{file_key}/nodes?ids{node_id}拿到指定节点的子树。我实际实验时只拿了登录页这个Frame的子树整个流程的请求量非常小。3.2 预处理把图层树压缩成LLM友好的文本格式拿到原始JSON之后不可能直接把它按原样丢给LLM。原始JSON里有大量冗余字段比如空数组的strokeWeights、没有意义的exportSettings、百八十个字符的name编码。更关键的是原始结构是带嵌套的但嵌套层级可能很深同一个Frame里有Group、Group里又套Frame、Frame里又套若干Rectangle。如果全部保留模型很容易被绕晕。我做的预处理分三块字段裁剪、坐标归一化、树结构文本化。字段裁剪的逻辑很简单只保留模型做语义判断时真正需要的字段。我保留了id、name、type、x、y、width、height、opacity、cornerRadius、fills中的颜色值、fontSize、fontName、characters、visible。像strokeWeights、blendMode、constraints这类字段对切图清单的生成没有帮助直接删掉。坐标归一化这一步很重要特别是处理嵌套图层时。Figma返回的absoluteBoundingBox是整个节点的绝对坐标但视觉上我们需要知道的是“相对父容器的相对坐标”和“整棵树的绝对坐标”。我两种都保留命名上明确区分。这样LLM在理解“这个按钮在输入框下方多少像素”时可以直接使用绝对坐标差值判断不需要心算。树结构文本化是我参考了YAML灵感后自己设计的一种格式。每个节点用缩进表示层级前缀标明类型后跟关键属性和坐标。下面是我实际用过的输出样例片段FRAME LoginScreen 375x812 GROUP LogoArea 120x120 (x127, y120) VECTOR logo_icon 80x80 (x20, y20) fill#4F46E5 TEXT app_name 200x30 (x-40, y92) fontInter-24 color#111827 FRAME InputGroup 343x200 (x16, y300) TEXT phone_label 100x20 (x0, y0) fontInter-14 color#6B7280 RECTANGLE phone_input 343x48 (x0, y24) radius8 fill#F3F4F6这种格式有几个好处一是token消耗少一个页面几十个节点也就几千token二是层次关系一目了然模型不需要从JSON的括号嵌套里推断父子关系三是它天然是“按视觉顺序排布”的我处理时会按y坐标从上到下、x坐标从左到右排序模型读到的顺序和人的视觉习惯一致语义归纳准确率明显提高。3.3 提示词设计让LLM输出可校验的切图清单提示词是整个实验里最重要的一环。我早期犯的错是把要求写得太抽象让模型“输出一份切图清单”结果它给我来了一堆模棱两可的话。后来我改成“固定格式约束少样本示例显式边界定义”三件套效果才稳定下来。核心提示词我贴在这里你可以直接抄走改改用你是一个UI切图助手。我会给你一棵UI图层树每个节点包含类型、名称、坐标和视觉属性。你的任务是把它整理成一份切图清单规则如下 1. 只输出JSON数组不要输出任何解释性文字。 2. 每一行节点对象必须包含id、name、type、group、x、y、width、height、exportType。 3. group字段表示该元素最终应该归入哪个导出目录如icons、images、background、components、text-style。 4. 只有满足以下条件之一的节点才需要出现在清单里包含位图填充的图层、独立矢量图标、需要切成图片的背景纹理、需要提取的组件容器。 5. 纯文字节点不放入清单但需要在remarks字段中记录其字体、字号、颜色方便后续生成样式代码。 6. 同组内节点按视觉层级排序最外层容器排最前。 7. 如果两个节点完全重叠且属于同一group只保留最上层的那个。然后我会给它一个两三个节点的示例示例的作用是让模型理解输出JSON的schema长什么样而不是让它照着套。示例给完后紧接着把预处理生成的树形文本贴进上下文。最后我会加一句硬性要求“请基于给出的精确坐标做判断不要猜测图层位置。”这句话是为了拦住模型在坐标推理上自由发挥虽然不能100%杜绝但明显减少了层级错乱的情况。4. 实操过程从设计稿到切图脚本的全流程4.1 准备示例界面一个手机登录页为了让整个实验可复现、可验证我特意挑了一个结构不算简单、但也不至于复杂到失控的界面手机App登录页。它包含的元素类型比较全背景渐变、Logo图标区、两个带标签的输入框、一个主登录按钮、一个底部文字链。视觉上大概在10个组件左右但图层树展开后实际节点数在35个左右因为有按钮的阴影层、输入框的内部结构、多段文字的分行图层等。这个规模的图层树预处理后只有大约3.8KB文本按token估算大概是1000个token左右塞进上下文完全没压力。如果你要处理的活动页、商城首页图层数可能会到200个以上这就需要做分块分析这个我在第5章的问题排查里会专门讲。4.2 预处理脚本完整实现下面是我实验中使用过的预处理脚本核心部分它不是最终成品但已经足够跑通全流程。脚本我放到一个类里封装方便后续复用。import json class LayerPreprocessor: def __init__(self): self.lines [] def _get_fill_color(self, node): for fill in node.get(fills, []) or []: if fill.get(type) SOLID and fill.get(visible, True): color fill.get(color, {}) r int(color.get(r, 0) * 255) g int(color.get(g, 0) * 255) b int(color.get(b, 0) * 255) return f#{r:02X}{g:02X}{b:02X} return def _get_font_style(self, node): style node.get(style, {}) font_name style.get(fontFamily, ) font_size style.get(fontSize, ) return font_name, font_size def _walk(self, node, depth0): node_id node.get(id, ) node_name node.get(name, ) node_type node.get(type, UNKNOWN) bbox node.get(absoluteBoundingBox, {}) x round(bbox.get(x, 0), 2) y round(bbox.get(y, 0), 2) w round(bbox.get(width, 0), 2) h round(bbox.get(height, 0), 2) visible node.get(visible, True) if not visible: return line f{ * depth}{node_type} {node_name} {w:.0f}x{h:.0f} (x{x:.0f}, y{y:.0f}) fill_color self._get_fill_color(node) if fill_color: line f fill{fill_color} if node_type TEXT: chars (node.get(characters, ) or ).strip().replace(\n, ) font_name, font_size self._get_font_style(node) line f font{font_name}-{font_size} text{chars[:20]} self.lines.append(line) for child in node.get(children, []): self._walk(child, depth 1) def to_text(self, root_node): self.lines [] self._walk(root_node) return \n.join(self.lines)这个脚本有几个细节是特意设计的。一是坐标全部四舍五入到整数因为LLM对浮点数的处理很弱0.00001和0对它来说没有区别保留太多小数纯属浪费token。二是如果节点不可见visible为false直接跳过不做任何语义分析这在设计稿里很常见很多设计师会放几个隐藏版本做备选这些绝对不能进入切图清单。三是TEXT节点会把文字内容截断到前20个字符防止品保文案过长污染视觉结构判断。4.3 调用LLM并校验输出结果预处理文本准备好之后接入LLM就非常简单了我直接使用了OpenAI兼容接口。这里要特别说一个点我强制要求模型输出严格的JSON数组而不是在对话流里夹带文字。最初我用普通对话方式模型偶尔会输出诸如“好的这是你要的切图清单”之类的前缀这种非结构化文本在后处理阶段非常讨厌。解决办法有两种一种是给API传response_format{type: json_object}另一种是在提示词末尾写死“只输出JSON数组”。实测下来双管齐下最稳。调用完成后我第一时间做的是JSON解析和结构校验而不是直接相信模型输出。校验分三层第一层是语法校验。用json.loads解析解析失败直接返回错误并让模型重新生成。第二层是字段完整性校验。每个节点对象必须包含id、name、type、group、x、y、width、height、exportType这些字段缺少任何一个就计入错误列表。第三层是几何合理性校验。比如坐标必须在父容器范围内、宽高必须是正数、两个同group的节点出现完全相同的坐标时只保留一个。这个校验非常关键因为模型偶尔会出现复制粘贴的问题把相邻两个节点的坐标写成完全一样的。4.4 根据清单自动导出资源与样式代码校验通过后的切图清单本质上已经是一个结构化数据了。有了这个数据导出资源的动作就变成了机械执行。我写了一个导出脚本逻辑大概是读取清单按group字段分类对每个节点调用Figma的图片导出API拿渲染链接下载后保存到对应目录。同时根据TEXT节点的字体信息生成一个包含字体、字号、颜色、间距的CSS变量文件。有一点必须诚实说明Figma的图片导出API需要传入节点id列表一次最多支持多个节点批量导出所以整个导出过程完全没有依赖LLM只是把LLM分析出来的id传给API。到这里整个“从设计稿到切图成品”的链路就闭合了Figma原始JSON → 预处理 → LLM分析 → 校验 → 自动化导出。整个流程跑完一个登录页大概需要40秒其中LLM推理占了大头但这个速度和人工切图相比已经是量级上的差距。5. 常见问题与排查技巧实录5.1 坐标理解偏差与容器嵌套混乱这类问题是我实验中遇到最多、也是最隐蔽的。LLM对于绝对坐标的解释能力比我想象中要弱它很容易把“子节点相对于父容器的内部坐标”和“子节点在整个页面上的绝对坐标”混在一起。在做预处理时我是两种坐标都保留的但输出格式里只保留了绝对坐标。按理说不会混但模型还是会在推断“某个元素是否超出父容器”时出错。我的解决办法是加了一个“上下文约束提示”在每个FRAME容器节点后面人工添加一行边界信息比如RELATIVE_TO_PARENT x0 y0 w375 h812。这样模型在判断子元素位置时能直接读到“这个容器的大小是375x812”不会自己脑补容器范围。加了这行之后我有一次测试里元素越界误判率从约80%降到了20%以内效果非常显著。5.2 Token超限与上下文截断页面图层多到一定程度比如超过200个节点的时候预处理文本会膨胀到15KB以上按token折算就是4000到5000个token。加上提示词和少样本示例单次请求很容易逼近模型上下文窗口的一半。这不是不能跑但会导致模型“顾头不顾腚”前面的图层记得清楚后面的图层开始糊弄甚至直接漏掉。遇到这种情况我的做法不是一味扩大上下文而是把页面按视觉区域切块。比如一个商城首页先按Frame拆成“顶部Header、商品卡片列表、底部TabBar”三个大块分别喂给模型做局部切图分析最后再合并结果。合并时要注意去重因为分块交界处可能会出现同一节点被两个块同时包含的情况我统一用node_id做去重保留第一次出现的结果。还有一个经验如果页面结构允许尽量把纯装饰性背景图层单独摘出去不参与LLM分析。背景图层通常是全尺寸的巨型色块或渐变它占据了大量token但对切图清单的贡献只有一个“background导出项”。把这部分在预处理阶段就摘掉能省下不少上下文空间。5.3 输出JSON语法错误与字段缺失模型输出JSON时偶尔会在数组末尾多一个逗号或者把双引号写成中文引号又或者在数组外面套了一层带说明的对象。这类问题不怪模型本质上是“生成文本”和“生成结构化数据”之间天然存在鸿沟。我的处理办法是写一个容错解析函数尝试json.loads失败后先用正则提取最外层方括号内的内容然后尝试修复明显的语法错误实在不行才重新请求模型。字段缺失的问题比语法错误更值得警惕。我遇到过一次模型把exportType写成了export_type因为少样本示例里不小心用了下划线命名。这个问题后来通过“输出schema前置声明强制检查”解决在提示词里把每个字段的示例值写全同时在程序里做枚举校验不在白名单里的字段一律算错并要求重新生成。5.4 复杂样式表达不清对于纯色图层、无阴影组件LLM的输出非常干净利落。但遇到渐变填充、多重阴影、混合模式这些复杂样式时用文本描述就会失真。比如一个按钮上有两层阴影一层是外发光、一层是投影模型在文本信息里看到的只是effects数组里两个对象它很难准确判断哪个是投影、哪个是外发光更不要说给出合理的CSS写法。我在处理这类问题时做了一个主动降级复杂样式字段在预处理时不展开细节而是用effectscomplex这种简写标记并在提示词里要求LLM把这类节点标记为needs_manual_check。这样至少保证这些节点不会被错误地清洗掉后续可以由人工集中审查。这个办法本质上是用“暴露问题”代替“试图让模型解决问题”在工程实践中反而更高效。5.5 图层命名混乱和分组失效设计稿的图层命名质量直接影响LLM的分组判断。设计师如果习惯用“矩形 28”、“组 12”这种自动命名LLM没法从名字中提取语义信息就只能靠坐标和视觉属性推断准确率会明显下滑。遇到这种情况我通常会做一次“命名无关化”处理预处理时把可识别的通用关键词保留比如tab、btn、icon、bg其余的普通名称统一改成layer_N这种序号命名。这样做的目的是逼迫模型不要依赖名字而是从位置和属性去推测元素在页面中的角色。我测试过一组命名混乱的真实登录页数据换了“命名无关化”处理之后分组准确率从63%提升到82%提升幅度相当可观。这说明LLM在“看图说话”这件事上比“看名字猜含义”要可靠得多所以遇到乱命名设计稿不要急着抱怨先把名字的作用降到最低。6. 实验结论与边界到底能不能用6.1 我能放心使用的场景整套实验跑下来我认为LLM直接做UI图层切图这件事在特定场景下是真实可用的不是噱头。第一类是“组件库规范化”场景。当团队里有一批历史设计稿需要把它们统一成组件库规范时LLM可以自动把散乱的图层归并成有语义的组件组并直接生成对应的组件骨架代码。第二类是“设计稿变更通知”场景。当设计师更新了设计稿可以自动对比差异LLM能准确说出“按钮宽度从120改成了144颜色从#4F46E5改成了#2563EB”这种变更说明对前端开发来说太有价值了。第三类是“快速原型”场景。给我一张设计稿的图层树LLM能在几十秒内给我一份结构化的切图清单和一份可用的CSS变量文件手动去做至少要半小时。在这三个场景里LLM输出的内容不需要100%精确因为有程序校验兜底而且最终会有人工抽检。换句话说它非常适合做“裁缝的学徒”——先把活干到80分再由人来精修。6.2 暂时不能依赖的场景我也踩过几次雷有些场景我真的不建议现在就把LLM推到一线。像素级精确还原场景是第一个不推荐项。当一个页面要求切图结果必须和设计稿完全像素一致时LLM的输出精度做不到坐标可能出现几位像素的偏移渐变和混合模式的处理更是没法保证。第二个是视觉极度复杂的页面比如数据大屏、3D效果展示页图层树动辄上千节点嵌套又深这超出了目前LLM能稳定处理的上下文范围。第三个是动效设计稿。动效稿里大量使用矢量路径、贝塞尔曲线、关键帧文本模型对这些数据的理解能力十分有限你还是得靠人工处理。6.3 这个实验后续还能怎么扩展虽然有一些场景暂时不能用但我对这个方向依然很看好。如果后续继续做我计划从两个方向深入一是引入多模态能力让LLM同时看设计稿截图和图层树文本视觉信息和结构信息互补应该能解决一部分坐标偏移问题二是让LLM和规则引擎、代码生成工具做深度配合LLM负责语义分析和结构化描述规则引擎负责精确计算代码生成器负责产出最终代码各自的强项组合起来。这个方向做好了前端从设计稿到页面代码的自动化程度会提高到一个新的水平。说实话这个实验的最终价值不在于“切图”本身而在于验证了一种协作模式模型负责理解程序负责精度人类负责决策。我在实际测试中发现把这三层分开之后每个环节的容错率都变得可控了。如果你也想做类似的事情我的建议是先挑一个最简单的页面跑通全流程再逐步往复杂场景推进中间踩坑的概率会小很多。