前端智能化——圖層抽象與優化

在設計稿生成代碼流程中,咱們須要先將圖層解析爲UI節點,然後再經過佈局算法生成代碼。前端

設計稿轉代碼基本流程node

做爲前端智能化的第一步,解析的UI數據關乎後續的代碼還原質量,所以須要一套方案來保證解析階段能輸出通用而有效的UI節點。算法

針對通用性和有效兩個目標,咱們將解析過程分爲圖層抽象和圖層優化兩個步驟。markdown

圖層抽象

爲了實現UI Nodes通用性,兼容不一樣的設計稿類型,如psd,sketch和xd等,咱們將設計稿的圖層抽象爲圖片Image、圖形Shape、文本Text三種類型的UI節點:數據結構

  1. Shape,可用樣式實現的形狀圖層,如純色帶邊框的矩形、圓角矩形、圓形等;ide

  2. Text,可用樣式實現的文本圖層;函數

  3. 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. 圖層合併;

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節點進行佈局到生成最終代碼。

更多關於前端智能化的課程,能夠參考我以前分享的課程:ke.qq.com/course/2995…

前端智能化 ——從圖片識別UI樣式​ :zhuanlan.zhihu.com/p/207308196

相關文章
相關標籤/搜索