之前分享了一篇关于如何实现瀑布流的demo。但瀑布流一般都是要结合滚动加载,因此今天补足这一块。完整demo可查看next-demo: next-demo - Gitee.com
示例图:
瀑布流滚动加载技术点
- 根据布局容器计算列宽和列数
- 根据列宽计算出图片渲染尺寸的宽高
- 在滚动加载组件里实现数据动态加载
- 容器宽度变化时,重新计算列宽和图片的渲染尺寸
滚动加载组件
- 使用视口监视器实现滚动加载,替代之前的监听滚动事件
- 数据加载结束,取消监视器监听事件
核心代码实现
- 根据容器宽度,定义布局列数,然后计算出列宽
//根据业务自行定义列数,边界值
const getColumnWidth = (node: HTMLElement, gap = 20) => {
let COLUMN_COUNT = 2;
if (node.clientWidth > 968 && node.clientWidth <= 1200 column_count='3;' else if node.clientwidth> 1200 && node.clientWidth <= 1400 column_count='4;' else if node.clientwidth> 1400 && node.clientWidth <= 1600 column_count='5;' else if node.clientwidth> 1600) {
COLUMN_COUNT = 6;
}
return {
column: COLUMN_COUNT,
columnWidth: Math.floor((node.clientWidth - gap * (COLUMN_COUNT - 1)) / COLUMN_COUNT),
}
}
- 根据列宽,实现图片渲染尺寸
const computedImageWH = async (url: string, width: number) => {
const img = new Image();
img.src = url;
await new Promise(res => img.onload = res);
return {
w: width,
h: Math.floor((img.naturalHeight / img.naturalWidth) * width),
src: img.src,
};
};
// 修改图片加载逻辑
const loadImages = async (data:any[]) => {
const newImages = await Promise.all(
data.map(async (item) => {
const res = await computedImageWH(item, columns.columnWidth);
return { ...res };
})
);
return newImages;
};
- 图片动态加载和布局定位计算
const calculatePositions = async(data:any[]) => {
const newPositions: Iposition[] = [];
const tempHeights = [...columnHeights.current];
const HEIGHT = (ITEM_PADDING * 2) + (BORDER_WIDTH * 2) + 20;
data.forEach((img) => {
// 计算元素高度
const elementHeight = img.h + HEIGHT
// 找到最小高度列
const minHeight = Math.min(...tempHeights);
const columnIndex = tempHeights.indexOf(minHeight);
// 计算位置
newPositions.push({
left: columnIndex * (columns.columnWidth + GAP),
top: minHeight
});
// 更新列高度(图片高度 + 间距)
tempHeights[columnIndex] += elementHeight + GAP;
});
columnHeights.current = tempHeights;
return newPositions;
}
- 滚动加载获取数据,扁平化和出栈处理
const getData = async()=> {
setLoading(true)
const res = await POST('/api/list/img')
if(res.code===0) {
const {list=[],hasMore} = res.data
if(hasMore) {
//将每次新数据保存在data中,
// 布局变化时,将data中的数据扁平化
// 滚动加载时,将data中的数据出栈,
// 然后重新计算布局
data.push(list)
setData([...data])
}
setHasMore(hasMore)
}else {
setLoading(false)
}
}
- 由于篇幅问题,滚动加载的组件暂不分享了,感兴趣的朋友可以看下完整demo