基于 React 的瀑布流组件
·
Yin灏
瀑布流布局的定义
一般来说,把多列等宽、元素高度不一的页面布局认为是瀑布流布局.
瀑布流的特点
瀑布流的特点通常包括以下:
- 多列布局
- 每列等宽
- 高度参差不齐(这是和常规列表不一样的地方,造成不规则的效果)
- 大量元素展示(通常是图片或图文并存)
因此,当我们根据获得的列表进行展示的时候,引申出排版的规律:
- 优先插入高度小的那一列
- 同等高度,优先左侧
动手开工
组件属性
根据上面的归纳,可以为Waterfall
组件得到以下属性
list
: 传入需要进行瀑布流布局的数据列表,每个元素必须包含 height 和 width 字段cols
: 瀑布流布局展示的列数width
: 瀑布流布局的总宽度,单位为 pxmargin
: 列与列之间的间隔距离,默认为 20,单位为 px
<Waterfall list={waterfallList} cols={4} width={450} margin={10}></Waterfall>;
计算每列的宽度
根据cols
、 width
和margin
,计算出每列的宽度。
const defaultColWidth = (width - (cols - 1) * margin) / cols;
分配&插入队列
接下来,要实现一个插入分配的函数。
- 依据
cols
的数目,创建一个用于存储元素的二维数组arr
- 创建一个用于记录每列高度的数组
- 遍历传入的
list
- 获取元素的
height
和width
字段 - 根据之前得到的每列宽度,计算出元素实际的显示高度
- 获取所有列的当前高度,把元素插入到高度最小列,并更新该列的宽度
- 获取元素的
- 返回所有元素都插入完成
arr
显示瀑布流
根据上一步得到的数组进行显示,大功告成。
代码展示
特别说明一下,这里有一个字段fields
,是作为业务展示数据传入,这里可以针对业务场景,做定制化的展示,这里就不再赘述。
function allocateItems(ls, len, colWidth) {
/** 各个瀑布流的内容列表 */
const arr = [];
/** 各个瀑布流的高度列表 */
const heightArr = [];
// 初始化瀑布流的内容列表和高度列表
for (let i = 0; i < len; i++) {
arr.push([]);
heightArr.push(0);
}
/** 获取高度最小的流的索引值 */
function getIndexOfMinHeightFlow() {
let minH = Number.MAX_SAFE_INTEGER;
let minIndex = 0;
heightArr.forEach((h, index) => {
if (h < minH) {
minH = h;
minIndex = index;
}
});
return minIndex;
}
// 通过计算展示高度,设置瀑布流的内容列表
ls.forEach((item, idx) => {
const index = getIndexOfMinHeightFlow();
item.displayHeight = (item.height * colWidth) / item.width;
arr[index].push(item);
heightArr[index] += item.displayHeight;
});
return arr;
}
const Waterfall = (props) => {
const { list, cols = 1, width, margin = 20, fields } = props;
const defaultColWidth = (width - (cols - 1) * margin) / cols;
const [colList, setColList] = useState(
allocateItems(list, cols, defaultColWidth)
);
useEffect(() => {
setColList(allocateItems(list, cols, defaultColWidth));
}, [list, cols, defaultColWidth]);
return (
<div className="waterfall" style={{ width: width + "px" }}>
{colList.map((col, fIndex) => (
<ul
className="waterfall-list"
style={{ width: defaultColWidth + "px" }}
key={"flow_" + fIndex}
>
{col.map((item, iIndex) => (
<li
className="waterfall-item"
key={
"flow_" + fIndex + "_item_" + iIndex + "_" + item.displayHeight
}
style={{ height: item.displayHeight + "px" }}
title={item.text || ""}
>
{/* 业务展示UI */}
</li>
))}
</ul>
))}
</div>
);
};
最后补充
上面的代码逻辑假定了已知元素的高宽,但是如果后端接口没有给我们图片大小的话,我们要怎样做呢?
可以是在allocateItems()
里面,添加new Image
请求图片并监听onLoad
事件,获取图片的实际大小再进行计算。