react-grid-layout是 React 生态中一个非常流行的、用于构建可拖拽 、可调整大小 、响应式网格布局的库。它的强大之处在于用简洁的 API 实现了复杂的布局管理。
一、 布局、坐标
react-grid-layout的实现基石在于它将组件的实际屏幕位置 与抽象的网格位置彻底分离
1. 布局数组
不直接存储组件的像素位置,而是维护一个名为 layout 的 JavaScript 对象数组
每个元素(item)在布局数组中都是一个对象,包含以下关键属性:
| 属性 | 类型 | 描述 |
|---|---|---|
i |
String | 元素的唯一 ID,对应于其 key 或子组件的 key。 |
x |
Number | 元素在网格中的列起始坐标(Grid X)。 |
y |
Number | 元素在网格中的行起始坐标(Grid Y)。 |
w |
Number | 元素的宽度,占用的网格列数。 |
h |
Number | 元素的高度,占用的网格行数。 |
static |
Boolean | 如果为 true,则元素不可拖拽和调整大小。 |
2. 坐标转换
核心逻辑在于将上述抽象的 (x, y, w, h) 网格坐标实时 转换成浏览器能理解的 CSS 像素坐标
该转换依赖于两个配置项:
cols: 网格的总列数rowHeight: 每行网格的高度(像素值)margin: 网格项之间的间隔(像素值)
<math xmlns="http://www.w3.org/1998/Math/MathML"> Item Width (px) = ( w × Cell Width ) + ( ( w − 1 ) × Margin ) \text{Item Width (px)} = (w \times \text{Cell Width}) + ((w-1) \times \text{Margin}) </math>Item Width (px)=(w×Cell Width)+((w−1)×Margin)
<math xmlns="http://www.w3.org/1998/Math/MathML"> Cell Width = Container Width − ( ( Cols + 1 ) × Margin ) Cols \text{Cell Width} = \frac{\text{Container Width} - ((\text{Cols} + 1) \times \text{Margin})}{\text{Cols}} </math>Cell Width=ColsContainer Width−((Cols+1)×Margin)
<math xmlns="http://www.w3.org/1998/Math/MathML"> Item Height (px) = ( h × Row Height ) + ( ( h − 1 ) × Margin ) \text{Item Height (px)} = (h \times \text{Row Height}) + ((h-1) \times \text{Margin}) </math>Item Height (px)=(h×Row Height)+((h−1)×Margin)
利用这些公式计算每个网格项的 top、left、width 和 height,并通过 CSS transform: translate(x, y) 来定位组件,而不是传统的 top/left,这能带来更好的性能。
二、 核心组件结构
主要由以下三个 React 组件构成:
1. ResponsiveReactGridLayout
这是最外层的容器组件。它负责处理响应式逻辑
- 监听窗口大小变化(
resize事件) - 根据当前的容器宽度,确定应该加载哪个断点(Breakpoint) (例如:
lg,md,sm等) - 根据断点和其对应的
layout配置,渲染ReactGridLayout
2. ReactGridLayout
这是网格渲染的核心组件,它负责:
- 计算和设置容器的总高度(
min-height),以确保所有网格项都能被容纳。 - 将
layout数组遍历,为每一个网格项渲染一个GridItem。 - 管理拖拽和调整大小操作的状态(影子/占位符)。
3. GridItem
这是每个可拖拽/调整大小的网格项的容器
- 渲染一个内部的
div来包裹用户传入的子组件 - 注入拖拽句柄 和调整大小句柄 。
- 通过监听鼠标事件(
onMouseDown/onMouseMove/onMouseUp)实现交互
三、 拖拽和调整大小原理
拖拽和调整大小依赖于两个库react-draggable 与react-resizable
1. 占位符与状态管理
当用户开始拖拽或调整大小时,不会立即更新 layout 状态,而是通过一种"影子"机制来优化性能和用户体验
- 占位符(Placeholder) : 一个半透明的、与当前操作网格项具有相同尺寸的元素会出现在其下方,指示操作完成后网格项将占据的位置
- 操作过程 : 在
onMouseMove过程中,RGL 实时计算鼠标位置对应的新网格坐标 <math xmlns="http://www.w3.org/1998/Math/MathML"> (new_x, new_y, new_w, new_h) \text{(new\_x, new\_y, new\_w, new\_h)} </math>(new_x, new_y, new_w, new_h),并更新占位符的位置 - 操作结束 : 只有在
onMouseUp释放时,RGL 才会调用onLayoutChange,将最终的网格坐标更新到父组件中
2. 网格冲突解决算法
- 冲突检测 : 检测新位置 <math xmlns="http://www.w3.org/1998/Math/MathML"> A' \text{A'} </math>A' 是否与任何其他网格项 <math xmlns="http://www.w3.org/1998/Math/MathML"> B \text{B} </math>B 发生矩形重叠。
- 向下推 : 如果发生冲突,会尝试将 <math xmlns="http://www.w3.org/1998/Math/MathML"> B \text{B} </math>B 以及与 <math xmlns="http://www.w3.org/1998/Math/MathML"> B \text{B} </math>B 冲突的其他网格项向下(增大 <math xmlns="http://www.w3.org/1998/Math/MathML"> y \text{y} </math>y 坐标)推动,直到不再发生冲突
- 紧凑化 : 在每次布局变化后,可以执行 Compaction 算法。它会尝试将所有非静态的网格项向上(减小 <math xmlns="http://www.w3.org/1998/Math/MathML"> y \text{y} </math>y 坐标)或向左(减小 <math xmlns="http://www.w3.org/1998/Math/MathML"> x \text{x} </math>x 坐标)移动到可用的最高/最左位置,从而消除网格中的不必要空白