可视化布局的新思路

3,620 阅读6分钟

首先你需要清楚,可视化布局low code是两个概念:

  • 可视化布局主要服务对象是前端,愿景是提升前端写静态页面的效率
  • low code则是降低编码门槛,自动生成可复用的代码

如果你每天要花大量时间写静态页面,应该会有一个感受,那就是根据设计稿一眼就能看出页面大概的布局,布局这部分的工作理论上是重复劳动,可为什么那些可视化布局的工具就是做不起来呢?

从一个简单的例子开始,假设我们要完成这样一个布局:

WechatIMG2.png

如果你熟悉flex,你会很快地把它拆解成下面这种结构(基于elment):

    <el-row type="flex" justify="start" align="middle">
        <el-col :span="18">
            <div>A</div>
            <div>B</div>
        </el-col>
        <el-col :span="6">
            <div>C</div>
        </el-col>
    </el-row>

如果使用可视化布局的工具来生成这些代码,需要干些什么呢,以bootstrap-layout为例

  • 拖一个一行两列的行容器
  • 左边的col,放两个控件
  • 右边的col,放一个控件

WechatIMG3.png

你可能会说,bootstrap-layout布局出来的代码只能用于bootstrap的项目中,如果我用的是element,这个工具对我而言就没法用了。

那找一个能生成element代码的布局工具,百度搜到的第一个是 form-generator

这个工具在这种场景下基本用不了,因为它的行容器只能在同一行。我们左边的col需要放两个垂直排列的文本,它做不到,所以只适用于一些表单的生成。

由于form-generator并没有文本控件,所以用单行输入框替代

WechatIMG4.png

可视化布局工具的问题

可用性

bootstrap-layout在布局上做的还算不错,至少在这种简单的场景下是可以满足的。但是对于现代的前端工程来说,由于技术栈的差异会导致最后生成的代码千差万别。例如:

  • 业务层框架不同。vue需要生成单文件组件,react需要生成单独的css文件
  • UI库不同,最后生成的代码标签就会不一样
  • 预处理器不同,sass,less,或者pug。又或者用的是windicss
  • 规范,代码风格不同,eslint,preitter的配置不同
  • 版本不同,vue2根vue3的代码差别就不小,用不用setup,用不用ts

如果不支持以上种种的配置项,那么对于可用性就要大打折扣。

效率

在简单的场景下,可视化工具代码的效率提升还是比较明显的,但是对于稍复杂一些的布局结构。例如:

WechatIMG5.png

bootstrap-layout需要做多层嵌套:

WechatIMG6.png

在row,col嵌套更深的情况下,用户体验相当糟糕。你要拖出这么一个嵌套的布局,然后在它生成的代码上做细节的修改来保证跟设计稿一致相当耗费精力,这些细节包括:

  • col之间的间隔无法调整,不支持offset (col距离左边的col几格)
  • 不支持col内部元素的位置改变,例如居左,居中,居右等等
  • col本身也不支持调整,只有固定的几种

新思路

从解决嵌套问题开始

每个人都有惯性思维,就好像你想到可视化布局,就会想到拖拽组件一样,只有能跳出惯性思维的束缚才又可能找到一个被隐藏的最优解。还是以这个结构为例。

WechatIMG2.png

看看你拆解这个布局的过程,我们把AB,看作是一个col,C看作是一个col,然后把AB,C放到一个row里面。那有没有可能,我就通过可视化布局生成三个单元格,按上图的方式排列,它就知道我需要生成什么样的结构,而不用我自己专门拖出row,col的控件呢?

答案是可行的,不过需要制定一套规则,例如:

  1. 当单元格的宽度相等,且相邻时合并成一个col
  2. 当单元格的高度相等,且相邻时合并成一个row

那么上面的这种结构就能通过这套规则自动合并,AB合并后,高度跟C相等,AB再与C合并,最终就生成了我们需要的结构。

当然这只是一个初步的设想,要满足大部分的布局这套规则还不够,我们不深入探讨这个算法。但是,我们通过一套规则解决了可视化布局中需要用户自己拖拽row,col,多层嵌套的问题。解决嵌套问题后我发现,这是一个极大的效率提升,特面是面对复杂布局的情况下,效率要提升80%以上。甚至由于你不用再嵌套了,这个界面跟预览的效果几乎一摸一样,真正的所见即所得。

顺着这个思路,这时我们要做的就是一个没有嵌套的二维布局,它可以通过单元格的宽高,位置自动合并。我们上面吐槽bootstrap-layout时说过,col本身无法调整,而且col之间的offset也无法调整,那么:

  • 假设单元格能随意缩放,我们就可以把单元格的宽度定为col的span(span代表一个col在24栅格中占几格)
  • 假设单元格的位置能随意摆动,我们就可以把单元格之间的间隔作为offset

这样对于细节上的修改工作就可以大幅减少。例如这种布局:

WechatIMG7.png

就能生成这样的代码:

    <el-row type="flex" justify="start">
        <el-col :span="8">
            <h3>span8</h3>
        </el-col>
        <el-col :span="8" :offset="8">
            <h3>span8</h3>
        </el-col>
    </el-row>

DEMO

这里有一个demo,是我正在开发中的,大家可以体验一下。开发中的可视化布局器

  1. 点击控件,或者点击生成单元格,按一定的布局排列他们
  2. 点击右上角的生成代码

对于这个项目如果你有好的建议,欢迎找我聊聊

如何解决可用性的问题?

我们已经知道了如何布局能提升效率,那如何才能根据具体的项目配置生成代码呢? 或许,我们可以参考babel的实现方案,babel将es6的代码转化为es5的过程:

  • 解析es6代码生成ast语法树
  • 遍历语法树,根据目标环境以及babel配置修改ast中的node,生成新的ast语法树
  • 根据traverse阶段生成的语法树,generate目标代码

对应可视化布局:

  1. 通过gird布局生成一个记录单元格的json
  2. 遍历json,根据每个单元格的位置,宽高横纵合并,往json中添加节点
  3. 根据项目配置生成目标代码

这样我们就把应用层框架,代码风格,UI库这些当成一个配置项,在最后生成代码的阶段去考虑。还是以这个简单的结构来说明这三步如何实现:

WechatIMG2.png

当你把三个单元格排列成这种布局时,生成的json是这样的:

[
    {
        "x": 0,   // 横坐标
        "y": 0,   // 纵坐标
        "w": 16,  // 宽度
        "h": 2,   // 高度
        "text": "A"
    },
    {
        "x": 0, 
        "y": 2, 
        "w": 16, 
        "h": 2, 
        "text": "B", 
    }
    {
        "x": 16,
        "y": 0,
        "w": 8, 
        "h": 4, 
        "text": "C"
    }
]

第二步,根据我们的合并规则,把这个结构转化成 (只显示了AB合并,以免json太长)

[
    {
        "x": 0, 
        "y": 0, 
        "w": 16, 
        "h": 4,  // 合并后高度变成4了
        "text": "col",
        "children":[]  // 记录ab的信息
    },
    {
        "x": 16, 
        "y": 0, 
        "w": 8, 
        "h": 4, 
        "text": "col",
        children: []  // 记录c的信息
    }
]

第三步,根据每个节点的标签,属性等生成代码。