最近在写项目的时候运用到了瀑布流布局,瀑布流布局是因其美观的视觉效果,在图片展示类中非常常见,比如说小红书App、微信视频号...
但你是否也曾遇到过 列高不均 的问题?导致瀑布流布局的某一列越来越高,让人看得非常难受。。。所以今天我们就来彻底解决它!
一、问题现象:瀑布流为什么会列高不均?
首先来看一下项目中的瀑布流实现。
这是一个mockjs模拟 API 数据的配置文件:
//非完整代码
const getImages = (page,pageSize=10) => {
return Array.from({length:pageSize}, (_, i) => ({
id: `${page}-${i}`,
height: Mock.Random.integer(400,600),
url: Mock.Random.image('300x400', Mock.Random.color(), '#fff', 'img'),
}))
};
...
response:({query})=>{
const page = Number(query.page) || 1;
return {
code: 0,
data: getImages(page)
}
}
将图片分为奇偶返回:
...
<div className={styles.column}>
{ images.filter((_, i) => i% 2 === 0 ).map(img => (
<ImageCard url={img.url} height={img.height} key={img.id}/>
))}
</div>
<div className={styles.column}>
{ images.filter((_, i) => i% 2 !== 0 ).map(img => (
<ImageCard url={img.url} height={img.height} key={img.id}/>
))}
</div>
这种实现方式非常的简单粗暴:
- 把 偶数 索引的图片放在第一列
- 把 奇数 索引的图片放在第二列
- 图片高度是随机生成的(400-600px)
这就导致了一个问题: 如果某一列连续出现多张高图片,这一列就会明显比另一列高 ,影响整体美观。
二、解决方案:3种方法让列高均匀
方案1:最小高度优先分配算法(推荐)
这是最常用也最有效的方法。核心思路是: 每次将新图片添加到当前高度最小的列。
实现步骤:
-
维护一个数组,记录每列当前的总高度
-
每次添加新图片时,找到高度最小的列
-
将图片添加到这一列,并更新该列的总高度
代码改造:
// 重新分配图片到不同列
useEffect(() => {
if (images.length === 0) return;
const newColumnHeights = [0, 0];
const newSortedImages = [[], []];
images.forEach(img => {
// 找到高度最小的列
const minColumnIndex = newColumnHeights[0] <= newColumnHeights[1] ? 0 : 1;
// 添加到该列
newSortedImages[minColumnIndex].push(img);
// 更新该列高度
newColumnHeights[minColumnIndex] += img.height;
});
setColumnHeights(newColumnHeights);
setSortedImages(newSortedImages);
}, [images]);
// 监听滚动加载更多的逻辑不变
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
fetchMore();
}
});
if (loader.current) observer.observe(loader.current);
return () => observer.disconnect();
}, [fetchMore]);
return (
<div className={styles.wrapper}>
<div className={styles.column}>
{sortedImages[0].map(img => (
<ImageCard url={img.url} height={img.height} key={img.id} />
))}
</div>
<div className={styles.column}>
{sortedImages[1].map(img => (
<ImageCard url={img.url} height={img.height} key={img.id} />
))}
</div>
<div ref={loader} className={styles.loader}>加载中...</div>
</div>
);
};
方案2:高度分组预分配
如果图片高度差异很大,可以尝试这种方法:
- 先将图片按高度排序
- 然后交替分配到不同列(高-低-高-低...)
这种方法可以确保每列都有高有低,避免某一列集中了太多高图片。
方案3:固定比例容器(简单但不灵活)
最简单的方法是给所有图片设置固定宽高比:
.card {
width: 100%;
padding-bottom: 150%; /* 固定宽高比为2:3 */
position: relative;
}
.img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
但这种方法会导致图片被裁剪,适合对图片展示要求不高的场景。
三、为什么最小高度优先算法最有效?
- 动态平衡 :实时调整每列高度,确保始终保持平衡
- 适应性强 :无论图片高度如何变化,都能很好地适应
- 资源利用率高 :充分利用每一列的空间
四、优化与注意事项
- 性能优化 :
- 如果图片数量很大,可以考虑使用虚拟列表
- 避免在滚动时频繁重新计算列高
- 图片加载 :
- 使用项目中已有的图片懒加载功能(
IntersectionObserver) - 确保图片加载完成后再更新列高
- 响应式布局 :
- 在不同屏幕尺寸下,可以动态调整列数
- 使用 CSS Grid 或 Flexbox 辅助布局
- 数据生成 :
- 当前项目中图片高度是完全随机的(400-600px)
- 可以考虑让高度有一定规律,避免极端情况
// 示例:生成更有规律的高度
height: 400 + (i % 5) * 50, // 400, 450, 500, 550, 600 循环
五、总结
瀑布流布局列高不均是一个常见问题,但通过最小高度优先分配算法可以很好地解决。这种方法实现简单,效果显著,是生产环境中最常用的解决方案。