在設計稿生成代碼流程中,咱們須要先將圖層解析爲UI節點,然後再經過佈局算法生成代碼。前端
設計稿轉代碼基本流程node
做爲前端智能化的第一步,解析的UI數據關乎後續的代碼還原質量,所以須要一套方案來保證解析階段能輸出通用而有效的UI節點。算法
針對通用性和有效兩個目標,咱們將解析過程分爲圖層抽象和圖層優化兩個步驟。markdown
爲了實現UI Nodes通用性,兼容不一樣的設計稿類型,如psd,sketch和xd等,咱們將設計稿的圖層抽象爲圖片Image、圖形Shape、文本Text三種類型的UI節點:數據結構
Shape,可用樣式實現的形狀圖層,如純色帶邊框的矩形、圓角矩形、圓形等;ide
Text,可用樣式實現的文本圖層;函數
Image,不可用樣式實現的圖層,如複雜圖形、帶紋理的形狀、位圖和藝術字等;oop
除了圖層類型抽象,其它圖層信息也將抽象爲圖元屬性,能夠分爲三種:佈局
基礎屬性,好比名字、id、圖層類型優化
位置屬性,好比寬高、座標
樣式屬性,描述圖層顏色和邊框等
UINode屬性
UINode類接口的具體代碼以下:
/**
* 圖層類接口
*/
interface UINode {
// 圖層id
id: string = '';
// 圖層類型,包括Text,Shape,Image
type: string;
// 圖層名稱
name: string = '';
// 寬度
width: number = 0;
// 高度
height: number = 0;
// 位置:距離左邊界距離
abX: number = 0;
// 位置:距離上邊界距離
abY: number = 0;
// 圖層樣式
styles: UIStyle = {};
}
複製代碼
解析後的圖層每每包含一些無效的信息,好比圖層冗餘、圖層零散的問題,咱們須要經過數據預處理來優化UI節點信息,提升代碼還原的精準度。
預處理階段主要分爲兩步:1. 圖層清洗 2. 圖層合併;
設計稿中會有不可見圖層,刪除它們不會影響視覺效果,這些圖層是冗餘的。
設計稿存在不可見圖層
圖層清洗,就是針對不可見的圖層進行剔除,分爲如下四種狀況:
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))
);
};
複製代碼
// 節點是否被覆蓋
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)影響到兄弟,則移除該節點
};
複製代碼
// 節點顏色是否與背景同色
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;
};
複製代碼
// 節點是否在邊界外
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;
}
}
複製代碼
這個步驟主要是判斷設計稿中哪些圖層須要合併,好比下圖的笑臉icon,若是不對圖層進行成組而直接導出,會輸出四張零散圖。
需合併的零散圖層
咱們判斷合併的思路是根據圖層之間空間關係是否相交,主要分爲如下兩步:
如上圖,圖形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)
);
複製代碼
咱們將相交關係的組(邊)進行合併,好比邊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節點進行佈局到生成最終代碼。
更多關於前端智能化的課程,能夠參考我以前分享的課程:ke.qq.com/course/2995…
前端智能化 ——從圖片識別UI樣式 :zhuanlan.zhihu.com/p/207308196