低代码网格布局
时隔一年,又重新参与低代码的建设,重新根据业务形态抽象网格布局逻辑。
那就来分享一下网格布局的实现逻辑。
网格布局
基础信息
设计将网格横向分为 12 列,并根据实际页面宽度(或者页面预设固定宽度)计算每列实际列宽,以作为 w
的单位映射值,即 w
最大为 12。
行高固定为 32px ,即 h
的单位映射值,不限制最大值。
格间间距为 16px 。
组件设计
GridItem
网格项
此组件会体现在 GridLayout
下,会对业务组件或基础组件等进行包裹,以保证 GridLayout
在渲染时,对 children
各组件进行包裹。
并且,此组件自身无 Schema
配置信息,仅从被包裹 Schema
中获取 size
以呈现组件具体大小(实际大小),或者映射大小(网格单位,通过计算呈现实际大小)。
GridLayout
网格容器
此组件为特殊内置组件(与业务组件或基础组件一致),具备 Schema
配置信息。
属性 | 备注 | 类型 |
---|---|---|
layouts |
children 中组件的位置信息 |
Array<LayoutItem> |
相关类型
// 设计参考 react-grid-layout 配置
interface LayoutItem {
id: string
x: number
y: number
w: number
h: number
gap: Record<'top' | 'right' | 'bottom' | 'left', number>
}
网格交互
网格交互设计分为:插入、移动、大小调整、删除
插入
获取环境信息
- 是否选中组件
LayoutItem
默认w
为 6 ,h
为 6
点击插入
- 若未选中组件,则默认插入根
GridLayout
- 更新
GridLayout
的layouts
下LayoutItem
数据至尾部(生成默认w
和h
) - 布局计算
- 若选中组件,获得选中组件信息
选中 GridLayout
- 更新
GridLayout
的layouts
下LayoutItem
数据至尾部(生成默认w
和h
) - 布局计算
选中业务组件或基础组件
- 获得选中组件
parent
- 若是
GridLayout
,则更新GridLayout
的layouts
下LayoutItem
数据至选中组件之后(生成默认w
和h
) - 布局计算
- 若不是则默认插入,不执行网格布局逻辑
选中容器组件
容器组件为特殊组件,即业务组件或基础组件支持
children
渲染
- 默认插入,不执行网格布局逻辑
移动插入
- 获得鼠标落点(鼠标抬起位置)
- 获得落点组件信息
- 逻辑同「点击插入」时选中组件情况
移动
- 获得鼠标落点(鼠标抬起位置)
- 获得落点组件信息
落点 GridLayout
- 检查落点
GridLayout
是否与当前移动组件的parent
一致 - 若一致则更新对应
layouts
下LayoutItem
数据至尾部 - 布局计算
- 若不一致则执行删除和插入(采用当前
w
和h
)操作
落点业务组件或基础组件
- 获得落点组件
parent
是否与当前移动组件的parent
一致 - 若一致则交换对应
layouts
下LayoutItem
数据(除了交换,也可以是插入,具体根据自身业务决定) - 布局计算
- 若不一致则执行删除和插入(采用当前
w
和h
)操作
落点容器组件
容器组件为特殊组件,即业务组件或基础组件支持
children
渲染
- 执行删除和插入(不执行网格布局逻辑)操作
- 布局计算
大小调整
- 获得鼠标
x
和y
的偏移量(鼠标按下到鼠标抬起后获得) - 根据当前组件
Schema
大小累加偏移量(四舍五入)计算组件新大小,即w
和h
的值 - 更新对应
layouts
下LayoutItem
数据 - 布局计算
删除
- 移除组件
Schema
- 移除对应
layouts
下LayoutItem
(若存在) - 布局计算(若存在)
布局计算
- 获得
layouts
信息 - 依次基于
w
和h
的值计算x
和y
位置 - 并且根据当前
w
、h
、x
和y
的关系计算格间间距值 - 更新
layouts
信息(格间间距在渲染、位置阴影、选中框时进行累加,不影响原值) - 渲染更新
算法根据自身业务决定,可以使用二维数组进行占位计算,或是使用自然堆叠计算方式,或是其他。
算法 A | 算法 B |
---|---|
算法 A
依次遍历
layouts
以返回新的x
和y
位置
- 初始化锚点
x
和y
位置(0, 0)
,行内标和行标(0, 0)
,缓存最大y
为 0 - 依次遍历,记录前一次的缓存最大
y
位置(用于计算格间间距) - 比较
LayoutItem
的x
与w
的和是否大于 12 - 若小于等于,则更新当前
LayoutItem
的x
和y
位置 - 同时更新锚点
x
的位置为当前LayoutItem
的x
与w
的和 - 根据当前
LayoutItem
的y
与h
的和,缓存最大y
位置(取最大值) - 若大于,则表示溢出,重置锚点
x
为 0 ,将锚点y
设置为缓存最大y
- 更新当前
LayoutItem
的x
和y
位置 - 重复 5, 6
- 每计算出
LayoutItem
的x
和y
位置后,比较前一次的缓存最大y
位置和缓存最大y
位置 - 若前一次的缓存最大
y
位置小于缓存最大y
位置(说明换行过),增加行标,重置行内标为 0 - 反之则增加行内标
- 将当前行内标和行标与
LayoutItem
对应关联
至此,已实现更新 x
和 y
位置,接下来结合行内标和行标信息来计算 LayoutItem
的格间间距。
倒序遍历行内标和行标
- 设置第一次右间距存在标识为否,前一次行标为行内标和行标信息的最后一个值
- 倒序遍历,获得对应的行内标和行标
- 初始化
LayoutItem
对应的格间间距,左右上为 0 ,下为 1/2 间距 8px - 若前一次行标与当前行标不一致(意味着行变更),则重置第一次右间距存在标识为否
- 更新前一次行标为当前行标
- 若当前行标大于 1 ,则表示非第一行,设置格间间距上为 8px
- 若第一次右间距存在标识为是,或者当前对应的
LayoutItem
的x
与w
的和小于 12 ,则设置格间间距右为 8px - 若当前行内标大于 0 ,则设置格间间距左为 8px ,并将第一次右间距存在标识设置为是
- 更新格间间距至对应的
LayoutItem
算法 B
依次遍历
layouts
以返回新的x
和y
位置
- 初始化空二维数组(记做标记空间),用于标记占位情况;
y
标识用于记录需要延展的行数,初始为 0 ,以及上一次的y
标识,同初始为 0 - 依次遍历,比较上一次的
y
标识与y
标识加LayoutItem
的h
,取最大值更新上一次的y
标识 - 使用上一次的
y
标识与标记空间长度比较,若大于 0 ,则批量生成行new Array(12).fill(null)
- 从
(0, 0)
标记位进行遍历(可优化至最左上角的一个null
标记位),寻找null
标记位 - 当找到
null
标记位后,以此标记位为起点,检查LayoutItem
的w
和h
占位情况(以标记位为起点向右下检查LayoutItem
的w
和h
长度,不能越界) - 若均为
null
则更新标记空间,并更新LayoutItem
的x
和y
为当前标记位起点,同时更新y
标识为当前标记位起点y
与LayoutItem
的h
的和,中断此次标记位遍历,进入下一个LayoutItem
遍历 - 反之寻找下一个
null
标记位起点
至此,已实现更新 x
和 y
位置,接下来结合标记空间来计算 LayoutItem
的格间间距。
- 遍历标记空间 4 条边,记录占用
LayoutItem
信息 - 依次遍历
LayoutItem
并初始化对应的格间间距,左右上下为 8px - 检查占用信息,对 4 条边存在占用的方向格间间距设置为 0
- 更新格间间距至对应的
LayoutItem
辅助功能
- 组件选中框跟随组件一起移动
- 计算组件预计落点位置阴影
- 动态替换
- 本文链接: https://zongzi531.com/2024/10/01/lowcode-grid-layout-rules/
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!