最近项目里有瀑布流的需求,本来打算使用现有轮子的,后来发现在兼容大小屏时样式会出现错乱,因此决定手动实现一个。以下一个简单的demo。(完整代码可已查看next-demo: next-demo - Gitee.com)
如上图所示,瀑布流实现的核心逻辑主要是根据容器宽度,排列数,自动计算图片宽度,然后计算各个图片的绝对定位位置。
技术要点
- 获取容器宽度,确定大小屏时图片的排列数(小屏2列,中屏3列,大屏4,超大屏5.。。)
- 根据容器宽度和列数计算出每项的宽度
- 根据每项宽度,使用New image计算出每个图片的宽高
- 将容器相对定位,每项绝对定位
- 计算每项绝对定位(left和top)
- 计算绝对定位时需要将每项的额外距离计算在内(padding,margin,border)
- 根据定位信息进行页面布局
代码实现
- 获取每项宽度
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 loadImage = 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 calculatePositions = (data:any[]) => {
const newPositions: Iposition[] = [];
const tempHeights = [...columnHeights.current];
data.forEach((img,index:number) => {
// 计算元素高度
const elementHeight = img.h + (ITEM_PADDING * 2) + (BORDER_WIDTH * 2) + 20;
// 找到最小高度列
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;
setPositions([...newPositions]);
}
- 完整的代码可查看 next-demo: next-demo - Gitee.com