简介
React-grid-layout 是一个基于 React 的网格布局库,它可以帮助我们轻松地在网格中管理和排列组件。它提供了一个直观的 API,可以通过配置网格的大小、列数和组件的位置来实现复杂的布局,还支持对网格中组件的拖拽和重新排列。
实现
诉求
-
绘制:编排之后,可以根据配置对页面进行正确的绘制
-
拖拽:支持位置调整,拖拽之后,调整对应配置参数
-
缩放:支持大小调整,缩放之后,调整对应配置参数
-
自动调整:当配置有错误、拖拽时有碰撞时,进行自动调整;布局空间的自动优化
示例:
https://react-grid-layout.github.io/react-grid-layout/examples/0-showcase.html
使用方式
const layout = [
{ i: 'a', x: 0, y: 1, w: 1, h: 1, static: true },
{ i: 'b', x: 1, y: 0, w: 3, h: 2 },
{ i: 'c', x: 4, y: 0, w: 1, h: 2 },
];
<GridLayout
layouts={layout}
margin={[10, 10]}
containerPadding={[10,10]}
cols=12
rowHeight=150
width=1200
style={
{}}
>
{layout.map((item) => (
<div key={item.i} className="item-style">
<div>{item.i}</div>
<div>
xy{item.x}/{item.y}
</div>
<div>
wh{item.w}/{item.h}
</div>
</div>
))}
</GridLayout>
绘制
容器
高度计算
const containerHeight = () => {
// autoSize的情况下,不计算容器高度
if (!this.props.autoSize) { return; }
// 底部坐标, 获取布局中y+h的最大值,即为容器的最大坐标h值
const nbRow = bottom(this.state.layout);
const containerPaddingY = this.props.containerPadding ? this.props.containerPadding[1] : this.props.margin[1];
// 计算包含margin/padding的真实高度
return ${nbRow * this.props.rowHeight + (nbRow - 1) * this.props.margin[1] + containerPaddingY * 2}px;
}
compact布局
如果直接安装上述的计算方式绘制,可能由于配置的问题,会带来页面的浪费。因此react-grid-layout会默认对纵向空间进行压缩,以避免空间的浪费,也支持设置为横向压缩,或者不压缩。在压缩过程中,很关键的一部分在于对模块间碰撞的处理。
function compact(layout, compactType, cols) {
// 对静态模块不进行压缩
const compareWith = getStatics(layout);
// 排序 horizontal | vertical
const sorted = sortLayoutItems(layout, compactType);
const out = Array(layout.length);
for (let i = 0, len = sorted.length; i < len; i++) {
let l = cloneLayoutItem(sorted[i]);
if (!l.static) {
l = compactItem(compareWith, l, compactType, cols, sorted);
compareWith.push(l);
}
// 放回正确的位置
out[layout.indexOf(sorted[i])] = l;
// 在处理冲突的时候设置flag
l.moved = false;
}
return out;
}
// 压缩方法
function compactItem(
compareWith: Layout,
l: LayoutItem,
compactType: CompactType,
cols: number,
fullLayout: Layout
): LayoutItem {
const compactV = compactType === "vertical";
const compactH = compactType === "horizontal";
if (compactV) {
// 垂直方向压缩,静态元素所需容器的高度 与 当前元素的纵向起始位置
l.y = Math.min(bottom(compareWith), l.y);
while (l.y > 0 && !getFirstCollision(compareWith, l)) {
l.y--;
}
} else if (compactH) {
while (l.x > 0 && !getFirstCollision(compareWith, l)) {
l.x--;
}
}
// 处理碰撞
let collides;
while ((collides = getFirstCollision(compareWith, l))) {
if (compactH) {
resolveCompactionCollision(fullLayout, l, collides.x + collides.w, "x");
} else {
resolveCompactionCollision(fullLayout, l, collides.y + collides.h, "y");
}
// 水平方向不超过最大值
if (compactH && l.x + l.w > cols) {
l.x = cols - l.w;
l.y++;
}
}
// 对上述的y--,x--做容错处理,确保没有负值
l.y = Math.max(l.y, 0);
l.x = Math.max(l.x, 0);
return l;
}
function getFirstCollision(
layout: Layout,
layoutItem: LayoutItem
): ?LayoutItem {
for (let i = 0, len = layout.length; i < len; i++) {
// collides方法:
// 当前节点 或者节点出现在另一个节点的上方(下边界 > 其他节点的起始位置)/下方/右侧/左侧
if (collides(layout[i], layoutItem)) return layout[i];
}
}
function resolveCompactionCollision(
layout: Layout,
item: LayoutItem,
moveToCoord: number,
axis: "x" | "y"
) {
const sizeProp = heightWidth[axis]; // x: w, y: h
item[axis] += 1;
const itemIndex = l