把 37 块设计稿搬进 Figma,逐像素证明没走样
Sketch 稿导进 Figma 后图层树完好,渲染却悄悄变了。我把验收标准定成逐像素比对,一直追到每一处差异都能解释为止。
- CATEGORY
- 工具
- YEAR
- 2026
- STATUS
- 笔记
- TOOLS
- python / numpy / pillow / figma
entry记录
一个 B 端产品的 PC 设计稿一直在 Sketch 里,团队要切到 Figma,理由是协作和交付,后面还要建设计 token(颜色、字号、间距这类设计变量)和组件库。迁移分 6 批,共 114 块画板。这里记录最后两批:第五批 35 块画板的收尾验收,第六批 2 块画板的完整迁移。起初我以为这是导入、对着看、有问题就改的体力活,实际动手才发现,靠肉眼对照行不通。
Figma 能直接导入 Sketch 文件,导入后图层树完整,图层名、可见性和层级都对得上,图层面板上看不出异常,渲染结果却不一样。这次遇到三个例子:一是弹窗主体的圆角没了,Sketch 把四个角的圆角存成 [0,0,12,12],提取脚本只读了第一个值,原本只有下面两个角是圆的,变成了四角全直;二是顶栏下方和侧栏右侧那条 1px 分隔线没了,设计稿里这条线不是描边,而是模糊为 0、偏移 1px 的内阴影,导入器把内阴影当成了普通外投影;三是搜索按钮的蓝色发闷,这块区域是一张截屏位图,图里带着一段 ICC 色彩描述文件(定义颜色在不同设备上如何显示的数据),Sketch 导出时遵守这段描述,Figma 导入时忽略它。47 张图里只有 6 张带这段色彩描述,恰好分布在残差最大的三块画板上;修完之后,其中一块画板上三张图的差异从 17,761px 降到 0。
分隔线差 1px,按钮颜色差一个色阶,圆角差 12px;一屏几十个元素,单看任何一块画板,肉眼都很难发现。但这些偏差会一直留在文件里,后面所有设计 token 和组件都建在这个基准上。所以验收标准定为:导出图和渲染图逐像素比对,差异收敛到每一处都能解释为止。靠肉眼判断「差不多」会一路漂移,第一块画板放过 1%,第十块就会放过 3%;有了数字,「还差多少」才能拿去汇报、排期。
比对方法试过两种。第一种是把两边的图层读出来,比坐标和尺寸;文本的几何可以完全对上,视觉上仍然大面积出错,因为出错的是填充、圆角、阴影、模糊这些不在包围盒里的属性。第二种是逐像素比对:Sketch 导出每块画板的 1 倍 PNG,Figma 请求 1:1 渲染,两张图逐像素算差异。
改成逐像素比对后,换字体带来的抗锯齿噪声和真实缺陷混在了一起。一块 1920×1080 的画板,光字体噪声就能占 2% 到 3%,真实缺陷可能只有 0.05%,看总数判断不出有没有在收敛。办法是做一层文本遮罩:从 Sketch 文件里抽出所有文本层的绝对包围盒,向外扩 3px,遮罩内的差异记作字体噪声,遮罩外的记作真实差异,验收只看后者。分开之后,每一轮修复让真实差异减少了多少像素可以直接读出来,也就知道先修哪一处。遮罩必须包含符号实例(Symbol Instance,即组件实例)里的文字;符号实例自身没有子图层,要按符号找到母版,再把母版里的文本框按实例的位置平移进来。补上这一步,某一批的真实差异从 8,492px 降到 2,281px;在这之前,logo 被误判成样式缺陷,追查了很久。
Sketch 文件其实是一个压缩包,里面的 JSON 就是完整的图层树。在本机用 Python 直接解析这份 JSON,比走 Sketch 的脚本接口快一个数量级,还能拿到接口不暴露的字段,圆角、模糊参数、内阴影的标志位都在里面。修复、截图、比对全由脚本驱动,按固定顺序跑:修复,截图,比对,看差异最大的几块,再接着修。顺序不能颠倒,比如把画板内容装进一个新建的帧(套帧)必须放在最后,因为套帧之后再读包围盒,拿到的是裁切后的值。差异数字只能指出哪里不对,裁图才能看出原因:颜色偏了、整体位移了、换行点变了,在数字上表现相同。每轮比对后看差异最大的三到五块裁图。
第五批发现内阴影全丢了:源文件里有 63 处,全是用内阴影画的 1px 发丝分割线。补回这 63 条内阴影后,差异从 233,046px 降到 204,043px,看起来可以收工。到第六批才发现,Figma 没有丢掉内阴影,而是把它当成普通外投影导入,颜色、偏移、模糊一字不改,只有类型由内变外。第五批的做法只对了一半:内侧该有的线补上了,外侧那圈本不该有的阴影一直留在文件里。正确的做法是补回内阴影的同时,删掉参数完全相同的那条外投影。第六批删掉 4 条,仅侧栏底部那一条就值 208px。一个属性在导入后不见了,要先查是真的没了,还是变成了别的属性,两种情况的修法正好相反。
Sketch 的帧有两种布局:一种是固定尺寸、会裁切内容的普通帧;另一种是自动布局(Stack),尺寸贴合内容,不裁切,文件里记的宽高往往比实际渲染范围小。我没分清这两种,给所有帧都加了裁切,误包了 79 个,某块画板的差异从 13,952px 涨到 77,716px,内容被切掉一大片。区分两者只要读一个字段 groupLayout._class,但在读到这个字段之前,比对跑了三轮,才把「修了反而更差」当作信号去查。一轮修复之后差异变大,就先回滚再找原因,不在上面再叠一轮修复。
第五批有一块 1712×2148 的高画板,里面只有两张截屏位图;修完所有已知问题后还差 80,026px,全部集中在下面那张图上。换图、把图转成 sRGB、去掉外层组、关掉裁切、换成一半分辨率的版本,数字都没有变化。随后把上下两张图的位置对调,糊的还是下面那张;再单独截下面那个节点,结果和基准逐像素一致,差异为 0。问题跟着位置走,不跟着节点或图片走:这是 Figma 截图接口在导出高画板时的降质,文件本身是对的,出问题的是测量方法。计量方式改成按节点分别截图再拼合后,这块画板从 80,026px 降到 0。排查到某个阶段,被测对象和测量方法都要怀疑:这 8 万像素差一点就被当成无法修复的残留写进了结论。
Figma 导入时会把中文自动换成另一款无衬线体,早期手册里写着「字重正确,不用管」。实测这条是错的:换成本机装着的、和源文件同一族的字体后,两块画板的差异分别从 49,424px 降到 28,819px、从 70,565px 降到 29,147px;逐节点比对,64 个文本节点里新字体在 52 个上差异更小。代价是新字体的中文标点是全宽,旧字体是比例宽度,长段落开始在不同的位置折行。
我选择整体换成最接近的那一款,个别折行的节点单独处理,不为了几十像素在一个设计文件里混用两种中文字体,因为两套字族给后面建 token 带来的麻烦,远大于那点像素差。受影响的节点只有 2 个,其中一个在设计稿里本来就是截断显示的,用「固定三行加末尾截断」修了回去;省略号的位置还差一个字,是已知的副作用,可以接受。另一个节点只是折行位置不同,修不了。
第五批 35 块画板从 271,455px 降到 85,670px,单板 0.000% 到 0.658%;第六批 2 块从 2,637px 降到 1,188px,单板 0.006% 和 0.051%。每一批遇到的问题都记进同一份手册,现在收录 31 类已知损失,每条写明怎么查、怎么修、值多少像素。前五批的问题都已写进手册,所以第六批最干净:0 处图层模糊、0 处渐进不透明度、0 处位图色彩调整、0 张带 ICC 的图、0 处画板位移,只有 2 个帧真的需要裁切。剩下的差异逐块看过裁图,全部属于两类:一类是位图的极端降采样,侧栏 logo 是一张 511×980 的图,放在 14×27 的位置上,压缩约 35 倍,长宽比还不一致,两个工具的重采样算法不同,边缘锯齿必然对不上;另一类是中文字形的抗锯齿,字的位置完全重合,只是边缘灰度不同。这两类在结构上修不掉,除非把位图整张换成在目标工具里重新渲染的版本,而那已经不算迁移。最后一块小画板剩 367px,放大看字位一致、形状一致,差的只是渲染噪声,到这个程度,我认为可以收工。
process对比
收敛过程
- 第五批 · 第 1 轮比对
- 271,455px
- 裁切宿主改绑背景矩形
- 233,046px
- 补回内阴影
- 204,043px
- 位图按 ICC 转 sRGB
- 165,696px
- 修正高画板的计量基准
- 85,670px · 单板 0.000%–0.658%
- 第六批 · 第 1 轮比对
- 2,637px
- 中文正文换字体
- 1,460px
- 删掉误导入的外投影
- 1,246px
- 按行数截断回流文本
- 1,188px · 单板 0.006% 和 0.051%
手册摘录(31 类已知损失中的几条)
- 图层模糊
- 导入后一个不剩 · 目标工具的半径 = 源文件半径 × 2 · 一批共 422 处
- 帧的边框
- 导入后一条不留 · 一批 99 个 · 补完后 6 块画板差异减半
- ICC 色彩描述
- 源文件导出时遵守,目标工具忽略 · 转换位图后归零
- 对角线性渐变
- 两边计算投影的坐标空间不同 · 56 处 · 8 块画板约 12,000 → 约 1,400px
- 编辑过锚点的椭圆
- 被导成标准椭圆 · 用锚点数据拼成矢量替换 · 30.6% / 36.2% → 0.4% / 1.1%
result小结
- 结论
- 第五批 35 块画板单板差异 0.000%–0.658%,第六批 2 块为 0.006% 和 0.051%;剩下的只有位图重采样和字形抗锯齿。
- 参数
- 文本包围盒外扩 3px 作字体噪声遮罩 · 遮罩外才算真实差异 · 手册收录 31 类已知损失
- 没做完
- 1 个折行点差异修不掉,省略号位置差一个字;位图极端降采样和中文抗锯齿在结构上无法消除。
log日志
- 2026-10
- 首次记录