前言
该项目基于react-grid-layout
和antd
的栅格布局实现。可能不适用所有项目, 但是思路也可以参考。
demo体验地址: 自定义布局
源码地址: 源码
实现功能
通过可视化拖拽区块, 组合区块生成自定义布局代码, 可直接引入有antd的项目中使用。
上面可视化操作后生成的布局代码:
<Row>
<Col span={10} className="drag-item">
<Col className="drag-subitem" style={{ height: '10vh' }} span={24}></Col>
<Col className="drag-subitem" style={{ height: '95vh' }} span={24}></Col>
</Col>
<Col span={14} className="drag-item">
<Col className="drag-subitem" style={{ height: '25vh' }} span={24}></Col>
<Col className="drag-subitem" style={{ height: '80vh' }} span={24}></Col>
</Col>
</Row>
代码生成的页面:
实现思路
要想生成任意布局, 栅格布局是最合适的。
这里不考虑特殊情况下的不规则布局。
我们先将布局按列划分, 每一列里面再分成几块, 这样基本就能满足我们大部分的业务场景了。
说起来简单,但是要和可视化拖拽结合起来就没那么简单了。
我们通过拖拽得到每个区块的宽高和位置信息, 借此来确定我们的区块应该放在哪个位置。
然后将其与antd的栅格布局的API结合, 生成我们想要的布局代码。
用过antd的栅格布局的应该知道, Row控制行布局, Col控制列布局。我的思路是:
整个页面都是一个Row, 然后根据拖拽信息划分列, 每个列里都是占满整个列宽的列, 这样就可以往下堆叠。
Col通过span
属性控制宽度, 高度我们直接通过style属性加上去。
实现
实现可视化拖拽
这里不得不介绍一下强大的react-grid-layout
库了。
git地址: react-grid-layout
我们只需要提供简单的基础信息就能生成可拖拽可伸缩的区块。 这里对API不做过多介绍, 主要将实现。
const dragItem = { x: 14, y: 14, w: 1, h: 1, maxH: 20 }
<GridLayout className="layout"
onResizeStop={onResizeStop}
onDragStop={onDragStop}
preventCollision={true}
margin={[0, 0]}
cols={12}
rowHeight={30}
width={800}
style={{ height: 600 }}
>
{
Object.keys(dragObj).map(item => <div className="drag-item" style={{ backgroundColor: '#' + ('00000' + (Math.random() * 0x1000000 << 0).toString(16)).slice(-6) }} key={item} data-grid={dragObj[item]}></div>)
}
</GridLayout>
数据驱动视图, 我们通过添加区块事件改变代码中的dragObj, 即可实现往视图中添加区块。
当我们改变了视图中的区块位置或大小时, 我们需要保存当前的区块信息, 方便我们接下来的生成布局代码。
上面代码中的onResizeStop
和onDragStop
事件可以帮我们监听我们的拖拽结束和改变区块大小的动作。
这两个事件做的事情是相同的, 记录当前区块状态, 这里看下事件代码:
const onResizeStop = (arr) => {
let obj = {}, layoutObj = {}
arr.forEach(item => {
obj[item['i']] = { x: item['x'], y: item['y'], w: item['w'], h: item['h'], maxH: item['maxH'] }
if (layoutObj[item['x']]) {
layoutObj[item['x']].push(item)
} else {
layoutObj[item['x']] = [item]
}
})
setLayoutObj(layoutObj)
setDragObj(obj)
}
这两个事件都会给我们提供一个包含所有区块信息的数组参数, 我们通过对这个参数做解析, 将其改造成我们可以使用的数据结构, 并保存在state中。
解析对象生成代码
当我们通过拖拽生成我们想要的数据结构后, 要生成我们想要的页面, 此时我们需要知道, 区块的位置和宽高如何映射到我们的布局中去。
我这里通过约定: 拖拽区域大小为宽800, 高600。 每个区块的基础划分:
将高度20等分, 宽度12等分。
这样:
一个单位的h对应0.5vh的高度;
一个单位的w对应2个单位的span(antd的Col的span是24等分)
有了映射关系, 我们只要把存储区块信息的对象改造成代码串就行了:
const onCodeGenerator = () => {
setCodeStr(
'<Row>\n'
+
Object.keys(layoutObj).map(item => `
<Col span={${layoutObj[item][0].w * 2}} className="drag-item">
${layoutObj[item].map(subItem => `
<Col className="drag-subitem" style={{height:'${subItem.h * 5}vh'}} span={24}></Col>\n`).join('')}
</Col>\n`
).join('')
+
'</Row>'
)
}
通过上面的代码进行改造, 就能得到我们想要的代码串, 直接复制, 粘贴到我们的项目中就得到了一个合适的布局。
后话
这算是一个工具性的东西, 我是根据当前公司业务的基础上进行的开发, 所以可能对大家的适用性不是很高, 这里记录下思路, 方便以后有类似的需求可以参考。