共计 4450 个字符,预计需要花费 12 分钟才能阅读完成。
在设计稿生成代码流程中,咱们须要先将图层解析为 UI 节点,而后再通过布局算法生成代码。
作为前端智能化的第一步,解析的 UI 数据关乎后续的代码还原品质,因而须要一套计划来保障解析阶段能输入通用而无效的 UI 节点。
针对通用性和无效两个指标,咱们将解析过程分为图层形象和图层优化两个步骤。
图层形象
为了实现 UI Nodes 通用性,兼容不同的设计稿类型,如 psd,sketch 和 xd 等,咱们将设计稿的图层形象为图片 Image、图形 Shape、文本 Text 三种类型的 UI 节点:
- Shape,可用款式实现的形态图层,如纯色带边框的矩形、圆角矩形、圆形等;
- Text,可用款式实现的文本图层;
- Image,不可用款式实现的图层,如简单图形、带纹理的形态、位图和艺术字等;
除了图层类型形象,其它图层信息也将形象为图元属性,能够分为三种:
- 根底属性,比方名字、id、图层类型
- 地位属性,比方宽高、坐标
- 款式属性,形容图层色彩和边框等
UINode
类接口的具体代码如下:
/**
* 图层类接口
*/
interface UINode {
// 图层 id
id: string = '';
// 图层类型,包含 Text,Shape,Image,Group
type: string;
// 图层名称
name: string = '';
// 宽度
width: number = 0;
// 高度
height: number = 0;
// 地位:间隔左边界间隔
abX: number = 0;
// 地位:间隔上边界间隔
abY: number = 0;
// 图层款式
styles: UIStyle = {};}
图层优化
解析后的图层往往蕴含一些有效的信息,比方图层冗余、图层零散的问题,咱们须要通过数据预处理来优化 UI 节点信息,进步代码还原的精准度。
预处理阶段次要分为两步:1. 图层荡涤 2. 图层合并;
1. 图层荡涤
设计稿中会有不可见图层,删除它们不会影响视觉效果,这些图层是冗余的。
图层荡涤,就是针对不可见的图层进行剔除,分为以下四种状况:
1.1 图层款式通明无背景;
const isTransparentStyle = function(node: UINode): boolean {const { background, border, shadows} = node.styles;
return (
!node.childNum
&& (node.isTransparent
|| (background
&& background.hasOpacity
&& background.type === 'color'
&& +background.color.a === 0)
|| (border && +border.color.a === 0)
|| (node.type === UINodeTypes.Shape && !background && !border && !shadows))
);
};
1.2 图层被其它图元笼罩;
// 节点是否被笼罩
const isCovered = function(node: UINode, nodelist: Array<UINode>): boolean {const index = nodelist.indexOf(node);
const arr2 = nodelist.slice(index + 1).filter(n => !isContained(n, node)); // 越往后节点的 z -index 越大
return arr2.some(brother => brother.type !== QNodeTypes.QLayer
&& isBelong(node, brother)
&& !brother.hasComplexStyle); // 如果节点被兄弟笼罩,并且本人没有其它属性(shadow)影响到兄弟,则移除该节点
};
1.3 图层色彩与底层图元色彩雷同;
// 节点色彩是否与背景同色
const isCamouflage = function(node: UINode, nodelist: Array<UINode>): boolean {const { pureColor} = node;
if (!pureColor) return false;
const nodeIndex = nodelist.indexOf(node);
const bgNode = nodelist
.slice(0, nodeIndex)
.reverse()
.find(n => isSameColor(pureColor, n.pureColor)
&& (!n.parent || isBelong(node, n)));
if (!bgNode) return false;
const bgNodeIndex = nodelist.indexOf(bgNode);
if (bgNodeIndex + 1 < nodeIndex) return !nodelist
.slice(bgNodeIndex + 1, nodeIndex)
.some(n => isIntersect(node, n));
return false;
};
1.4 图层位于可视边界外
// 节点是否在边界外
const isOutside = function(node: UINode, rootNode: UINode): boolean {
return !(
node.abX >= rootNode.abXops
|| node.abY >= rootNode.abYops
|| node.abX >= rootNode.abXops
|| node.abY >= rootNode.abYops
);
};
咱们定义为一个荡涤函数,输出图层节点列表遍历,如果满足上述四个条件之一,则过滤掉该节点。
// 图元冗余荡涤
function clean(nodes: UINode[]) {const [rootNode] = nodes;
return nodes.filter((node: UINode) => {
const needClean =
isTransparentStyle(node) // 节点是否款式不可见
|| isOutside(node, rootNode) // 节点是否位于边界外
|| isCovered(node, nodes) // 节点是否被笼罩
|| isCamouflage(node, nodes); // 节点是否色彩假装
// 满足其中一种状况则视为冗余节点
return !needClean;
}
}
2. 图层合并
这个步骤次要是判断设计稿中哪些图层须要合并,比方下图的笑脸 icon,如果不对图层进行成组而间接导出,会输入四张零散图。
咱们判断合并的思路是依据图层之间空间关系是否相交,次要分为以下两步:
2.1 判断两节点之间的相交关系
如上图,图形 eye 和 face 相交,mouth 和 face 相交,失去相交关系 A:[eye,face],相交关系 B:[mouth,face] 两个组,代码如下:
let isCollision = (node: UINode, brother: UINode) => !((node.abY + node.height < brother.abY) || (node.abY > brother.abY + brother.height) ||
(node.abX + node.width < brother.abX) || (node.abX > brother.abX + brother.width)
);
2.2 多个节点合并
咱们将相交关系的组(边)进行合并,比方边 A 中的 face 图层在 B 关系中也存在,那么将 A 和 B 进行合并,失去 C:[eye, face, mouth]。
mergeJudge(nodelist: UINode[]): Array<Set<UINode>> {
// 相交检测
const groups: Array<Set<UINode>> = [];
const relations = [];
for (let i = 0; i < nodelist.length; i++) {const node = nodelist[i];
for (let j = i + 1; j < nodelist.length; j++) {const brother = nodelist[j];
if (isCollision(node, brother)) { // 判断两节点是否相交
relations.push([node, brother]); // 相交则退出边列表
}
}
}
// 关系聚合
relations.forEach(([node, brother]) => {
// 查找以后边的两个端点是否曾经有过成组
let res = groups.filter(group => group.has(node) || group.has(brother));
if (res.length) { // 已成过组
const unionGroup = res.reduce((p, c) => p.concat([...Array.from(c)]), []);
res.forEach(g => groups.splice(groups.indexOf(g), 1)); // 剔除原有组
groups.push(new Set(unionGroup).add(node)
.add(brother)); // 合并新组
} else groups.push(new Set([node, brother])); // 否则,自成新组
});
return groups;
}
}
最初依据将这些关系合并成新的节点:
// 零散图元合并
function merge(nodes: UINode[]) {
// 依据空间关系合并图层
if (!nodes.length) return;
const groupArr = mergeJudge(nodes); // 碰撞检测,输入成组列表 [[node1,node2],[node3,node4],node5]
groupArr.map((item: UINode | UINode[]) => {if (item.size > 1) {const newNode = union([...item], UINodeTypes.Image); // 合并成图片节点
return newNode;
}
return item;
});
});
总结
本文通过图层形象和优化两个步骤,形象过程是将不同设计软件图层解析为对立的数据结构,接着通过图层优化,革除冗余节点和合并零散节点,失去“洁净”的 UI 节点汇合。
后续咱们将介绍如何利用这些 UI 节点进行布局到生成最终代码。
更多对于前端智能化的课程,能够参考我之前分享的课程:https://ke.qq.com/course/2995626
文章传送:《前端智能化 ——从图片辨认 UI 款式》https://zhuanlan.zhihu.com/p/…
各个设计平台的解析文档如下:
Sketch API: https://developer.sketch.com/reference/api/
PhotoShop API: https://www.adobe.com/devnet/photoshop/scripting.html
XD API: https://adobexdplatform.com/plugin-docs/reference/how-to-read.html