瀑布流又称瀑布流式布局,是比较流行的一种网站页面布局方式,视觉表现为参差不齐的多栏布局。最早采用此布局的是网站是 Pinterest,后逐渐在国内流行,比如
花瓣,
蘑菇街等。
瀑布流布局效果
css flex布局实现方式
最外面是一个容器,设置display为flex,使容器内部div横向排列,形成列,div内部通过flex-column,纵向排列。
<div class="tab-container">
<div class="column">
<div class="tab-item">1</div>
<div class="tab-item height50">2</div>
</div>
<div class="column">
<div class="tab-item">3</div>
</div>
<div class="column">
<div class="tab-item height50">4</div>
<div class="tab-item">5</div>
<div class="tab-item height50">6</div>
</div>
</div>
* {
margin: 0;
padding: 0;
}
.tab-container {
display: flex;
}
.column {
flex-grow: 1;
margin-right: 20px;
display: flex;
flex-direction: column;
}
.column:last-of-type {
margin-right: 0;
}
.tab-item {
height: 100px;
background: white;
margin-bottom: 10px;
}
.height50 {
height: 50px;
}
样式结果如下图
优点:
能轻松实现瀑布流静态效果,且性能不错。
缺点:
- 在填充元素前,需要对数据进行分列处理。
- 还需要手工对已经分完列的数据进行适当的调整,以防止相邻列内容高度差距过大。
- 若是动态添加的元素,则还是需要进行第一步的处理,同时对第二步的处理就无能为力了。
css column属性实现方式
多行等宽元素排列,后面的元素依次添加到其后,等宽不等高,根据图片原比例缩放直至宽度达到我们的要求,依次放入。
html部分
<div class="tab-container">
<div class="tab-item">tab1</div>
<div class="tab-item height50">tab2</div>
<div class="tab-item height50">tab3</div>
<div class="tab-item">tab4</div>
<div class="tab-item height50">tab5</div>
<div class="tab-item">tab6</div>
<div class="tab-item">tab7</div>
<div class="tab-item">tab8</div>
<div class="tab-item height50">tab9</div>
<div class="tab-item">tab10</div>
</div>
css部分
* {
margin: 0;
padding: 0;
}
.tab-container {
column-count: 3;
column-gap: 30px;
}
.tab-container .tab-item{
height: 150px;
margin-bottom: 20px;
break-inside: avoid;
border: 1px solid #000;
text-align: center;
background: white;
}
.tab-container .height50 {
height: 100px;
}
样式结果如下图
优劣: 能轻松实现瀑布流静态效果,且性能不错。缺点是后续动态添加的内容,会在最右侧依次添加,整体交互体验不符合实际需求。故需要js处理动态添加的内容。
那么js处理的规则是什么呢?
下面通过图解来分析一下瀑布流的算法。
图解瀑布流算法
当第一排排满足够多的等宽图片时(如下图情况),自然而然的考虑到之后放置的图片会往下面排放。
那么第六张图片,放置在什么位置呢?是下图的位置么?
我们通过瀑布流算法实验得到,后面紧跟的第六张图片的位置应该是这个位置。
为什么呢?
因为放置它之前,这一列的高度为所有列中最小,所以会放置在这个地方。
所以我们知道了,如果再继续放置下去,第七张图片应该是这个位置,对吗?
通过瀑布流算法实验得出位置正确。看懂这个图示应该就能理解了瀑布流的原理算法。
JS方式实现瀑布流
思路分析:
-
构建成行元素 + 寻找新增元素追加位置
瀑布流所有元素的宽度是固定的,我们用浏览器的宽度除以每个瀑布流块的宽度,就是每一行可容纳的瀑布流块的个数。因为每个瀑布流块的高度不一,我们姑且把组成一行的这组元素称为成行元素,在成行元素放置完毕后,我们如果要再增加一个元素,那么它的位置应该这样找?
获取成行元素集合中高度最低的那个元素,待放置的元素的top值应该是这个最低元素的高,left值应该是这个最低元素的left值。
这样,新增的这一个元素我们就找到了它存放的位置,这样成行元素中的最低高度值就变为了原来的高度+新增元素的高度。
-
重复1,依赖成行元素追加新元素。
1中我们已经实现了一次成行元素追加一个新的元素,这样新元素增加之后我们就构建了新的成行元素,之后的操作就是在新的成行元素中追加新元素,原理同1。
html部分
<div class="tab-container" id="tabContainer">
<div class="tab-item">
<img src="http://n.sinaimg.cn/photo/700/w1000h500/20210603/57a6-kracxep9657657.png" />
</div>
<div class="tab-item">
<img src="http://n.sinaimg.cn/ent/4_img/upload/0b3147ad/294/w690h1204/20210905/0176-10a73c9a66bef6da4e151590480fb865.jpg" />
</div>
<div class="tab-item">
<img src="http://n.sinaimg.cn/photo/700/w1000h500/20210603/57a6-kracxep9657657.png" />
</div>
<div class="tab-item">
<img src="http://n.sinaimg.cn/ent/4_img/upload/0b3147ad/294/w690h1204/20210905/0176-10a73c9a66bef6da4e151590480fb865.jpg" />
</div>
<div class="tab-item">
<img src="http://n.sinaimg.cn/ent/4_img/upload/0b3147ad/294/w690h1204/20210905/0176-10a73c9a66bef6da4e151590480fb865.jpg" />
</div>
<div class="tab-item">
<img src="http://n.sinaimg.cn/photo/700/w1000h500/20210603/57a6-kracxep9657657.png" />
</div>
<div class="tab-item">
<img src="http://n.sinaimg.cn/photo/700/w1000h500/20210603/57a6-kracxep9657657.png" />
</div>
<div class="tab-item">
<img src="http://n.sinaimg.cn/ent/4_img/upload/0b3147ad/294/w690h1204/20210905/0176-10a73c9a66bef6da4e151590480fb865.jpg" />
</div>
<div class="tab-item">
<img src="http://n.sinaimg.cn/photo/700/w1000h500/20210603/57a6-kracxep9657657.png" />
</div>
<div class="tab-item">
<img src="http://n.sinaimg.cn/ent/4_img/upload/0b3147ad/294/w690h1204/20210905/0176-10a73c9a66bef6da4e151590480fb865.jpg" />
</div>
<div class="tab-item">
<img src="http://n.sinaimg.cn/photo/700/w1000h500/20210603/57a6-kracxep9657657.png" />
</div>
<div class="tab-item">
<img src="http://n.sinaimg.cn/ent/4_img/upload/0b3147ad/294/w690h1204/20210905/0176-10a73c9a66bef6da4e151590480fb865.jpg" />
</div>
<div class="tab-item">
<img src="http://n.sinaimg.cn/photo/700/w1000h500/20210603/57a6-kracxep9657657.png" />
</div>
</div>
css部分
* {
margin: 0;
padding: 0;
}
.tab-container {
position: relative;
}
.tab-container .tab-item {
position: absolute;
height: auto;
border: 1px solid #000;
background: white;
break-inside: avoid;
text-align: center;
}
.tab-container .tab-item img {
width: 100%;
height: auto;
}
js部分
window.onload = function () {
waterFall('#tabContainer', '.tab-item'); //实现瀑布流
}
/**
* @param { string } wrapIdName 容器id(或class)名称
* @param { string } contentIdName 容器中内容项id(或class)名称
* @param { number } column 容器中内容展示列数 默认为4
* @param { number } columnGap 容器中列间隔距离 默认为20
* @param { number } rowGap 容器中行间隔距离 默认为20
*/
function waterFall(wrapIdName, contentIdName, columns = 4, columnGap = 20, rowGap = 20) {
// 获得内容可用宽度(去除滚动条宽度)
const wrapContentWidth = document.querySelector(wrapIdName).offsetWidth - 8;
// 间隔空白区域
const whiteArea = (columns - 1) * columnGap;
// 得到每列宽度(也即每项内容宽度)
const contentWidth = parseInt((wrapContentWidth - whiteArea) / columns);
// 得到内容项集合
const contentList = document.querySelectorAll(contentIdName);
// 成行内容项高度集合
const lineConentHeightList = []
for (let i = 0; i < contentList.length; i++) {
// 动态设置内容项宽度
contentList[i].style.width = contentWidth + 'px';
// 获取内容项高度
const height = contentList[i].clientHeight;
if (i < columns) {
// 第一行按序布局
contentList[i].style.top = 0;
contentList[i].style.left = contentWidth * i + columnGap * i + 'px';
// 将行高push到数组
lineConentHeightList.push(height)
} else {
// 其他行
// 获取数组最小的高度 和 对应索引
let minHeight = Math.min(...lineConentHeightList)
let index = lineConentHeightList.findIndex(listH => listH === minHeight)
contentList[i].style.top = (minHeight + rowGap) + 'px';
contentList[i].style.left = (contentWidth + columnGap) * index + 'px';
// 修改最小列的高度 最小列的高度 = 当前自己的高度 + 拼接过来的高度 + 行间距
lineConentHeightList[index] += height + rowGap
}
}
}
代码处理结果