基于react的瀑布流组件
在日常工作的过程中,搭建瀑布流式的页面布局是一个经常出现的业务场景。
背景
瀑布流布局的定义
一般来说,把多列等宽、元素高度不一的页面布局认为是瀑布流布局.
瀑布流的特点
瀑布流的特点通常包括以下:
- 多列布局
- 每列等宽
- 高度参差不齐(这是和常规列表不一样的地方,造成不规则的效果)
- 大量元素展示(通常是图片或图文并存)
因此,当我们根据获得的列表进行展示的时候,引申出排版的规律:
- 优先插入高度小的那一列
- 同等高度,优先左侧
动手开工
组件属性
根据上面的归纳,可以为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
事件,获取图片的实际大小再进行计算。
转载自:https://juejin.cn/post/7151429223129808903