瀑布流的多种实践
可实现方案:flex弹性布局;column多行布局;js计算的两种方案
1.flex弹性布局;
将外层父元素设置display:横向布局 ,再设置 flex-flow:column wrap 纵向布局且换行即可
.container {
width: 100%;
height: 70vh;
display: flex;
flex-flow: column wrap;
.item {
width: calc((100% - 60px) / 4);
height: 10vh;
background: rgba(12, 50, 97, 0.8);
border: 1px solid #9cdeff;
color: #fff;
margin: 10px;
display: flex;
justify-content: center;
align-items: center;
&:nth-of-type(2n),
&:nth-of-type(5),
&:nth-of-type(11) {
height: 15vh;
}
}
2.column多行布局;
column 实现瀑布流主要依赖两个属性。 一个是 column-count 属性,是分为多少列。 一个是 column-gap 属性,是设置列与列之间的距离。
注意防止断点,否则dom会显示不全。
.container {
width: 100%;
column-count: 5
column-gap: 10px;
.item {
height: 10vh;
background: rgba(12, 50, 97, 0.8);
border: 1px solid #9cdeff;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 10px;
break-inside: avoid; /*防止断点*/
&:nth-of-type(2n),
&:nth-of-type(5),
&:nth-of-type(11) {
height: 15vh;
}
}
}
}
未设置:break-inside: avoid:
设置break-inside: avoid后:
对于将文字分列展示挺合适:
3.js计算
无论是flex还是column,用css写瀑布流,每一块都是从上往下排列,不能做到从左到右排列,并且不会识别哪一块图片放在哪个地方合适,对于需要动态添加数据的情况效果并不好。
我们可以通过用JS 计算来解决,我们先来看一种方案:
思路分析
-
瀑布流布局的特点是等宽不等高。
-
为了让最后一行的差距最小,从第二行开始,需要将图片放在第一行最矮的图片下面,以此类推。
-
父元素设置为相对定位,图片所在元素设置为绝对定位。然后通过设置 top 值和 left 值定位每个元素。
<script type="text/javascript">
2 // 定义瀑布流算法函数
3 function fall() {
4 const minGap = 20; // 最小间距,让每一列的最小空隙可以自定义,避免太过拥挤的情况发生。但是,会通过计算得到真实的间距。
5 const itemWidth = 300; // 每一项的宽度,即当前每一个图片容器的宽度。保证每一列都是等宽不等高的。
6 const scrollBarWidth = getScrollbarWidth(); // 获取滚动条的宽度
7 const pageWidth = window.innerWidth - scrollBarWidth; // 获取当前页面的宽度 = window.innerWidth - 滚动条的宽度
8 const column = Math.floor(pageWidth / (itemWidth + minGap)); // 实际列数=页面宽度/(图片宽度+最小间距)
9 const gap = (pageWidth - itemWidth * column) / column/2; // 计算真实间距 = (页面宽度- 图片宽度*实际列数)/实际列数/2
10 const items = document.querySelectorAll('img'); // 获取所有的外层元素
11 const heightArr = []; // 定义一个空数组,保存最低高度。
12
13 // 获取滚动条的宽度
14 function getScrollbarWidth() {
15 const oDiv = document.createElement('div');//创建一个div
16 // 给div设置样式。随便定义宽高,只要能获取到滚动条就可以
17 oDiv.style.cssText = `width: 50px;height: 50px;overflow: scroll;`
18 document.body.appendChild(oDiv);//把div添加到body中
19 const scrollbarWidth = oDiv.offsetWidth - oDiv.clientWidth;// 使最大宽度和可视宽度相减,获得到滚动条宽度。
20 oDiv.remove();//移除创建的div
21 return scrollbarWidth;//返回滚动条宽度
22 }
23
24
25 for (let i = 0; i < items.length; i++) {
26 // 遍历所有的外层容器
27 const height = items[i].offsetHeight;
28 // 如果当前处在第一行
29 if (i < column) {
30 // 直接设置元素距离上部的位置和距离左边的距离。
31 items[i].style.cssText = `top: ${gap}px;left: ${(itemWidth + gap) * i + gap}px`;
32 // 保存当前元素的高度。
33 heightArr.push(height);
34 } else {
35 // 不是第一行的话,就进行比对。
36 let minHeight = heightArr[0]; // 先保存第一项的高度
37 let minIndex = 0; // 保存第一项的索引值
38 for (let j = 0; j < heightArr.length; j++) {
39 // 通过循环遍历比对,拿到最小值和最小值的索引。
40 if (minHeight > heightArr[j]) {
41 minHeight = heightArr[j];
42 minIndex = j;
43 }
44 }
45 // 通过最小值为当前元素设置top值,通过索引为当前元素设置left值。
46 items[i].style.cssText = `top: ${minHeight + gap *2}px; left: ${(itemWidth + gap) * minIndex + gap}px`;
47 // 并修改当前索引的高度为当前元素的高度
48 heightArr[minIndex] = minHeight + gap + height;
49 }
50 }
51 }
52 // 页面加载完成调用一次。
53 window.onload = fall;
54 // 页面尺寸发生改变再次调用。
55 window.onresize = fall;
56 </script>
上面这种方案HTML结构较简单 适合绝大多数情况。
但对于需要横向排序号的dom,定位后无法正确排序,对于这种业务需求,可以采用另一种方案:
思路分析
-
瀑布流布局的特点是等宽不等高。
-
需要多少列就建立多少个dataList 。从第二行开始,每次将数据push到高度最短的列里面,以此类推。
-
每项的序号为 m*idx+n ( m为总列数 n为该项位于的列数)
具体实现
1.页面布局结构代码
<div class="box"> <div class="col" ref="col1"> <transition-group name="list"> <div class="item" v-for="item in dataList1" :key="item.id">{{ 4 * imgIdx + 1 }}</div> </transition-group> </div> <div class="col" ref="col2"> <transition-group name="list"> <div class="item" v-for="item in dataList2" :key="item.id">{{ 4 * imgIdx + 2 }}</div> </transition-group> </div> <div class="col" ref="col3"> <transition-group name="list"> <div class="item" v-for="item in dataList3" :key="item.id">{{ 4 * imgIdx + 3 }}</div> </transition-group> </div> <div class="col" ref="col4"> <transition-group name="list"> <div class="item" v-for="item in dataList4" :key="item.id">{{ 4 * imgIdx + 4 }}</div> </transition-group> </div> </div>
2.js代码
mountMenu(arg) {
if (this.mainMenuList.length <= 4) {
//数据不超过一行时 特殊处理
if (this.mainMenuList[0]) {
this.dataList1.push(this.mainMenuList[0])
}
if (this.mainMenuList[1]) {
this.dataList2.push(this.mainMenuList[1])
}
if (this.mainMenuList[2]) {
this.dataList3.push(this.mainMenuList[2])
}
if (this.mainMenuList[3]) {
this.dataList4.push(this.mainMenuList[3])
}
return
}
var temp = this.mainMenuList
var index = arg || 0
var refName = this.selectCol()
if (temp.length > index) {
//把数据push到前高度最小的列对应的dataList中
this[refName].push(this.mainMenuList[index])
this.$nextTick(() => {
//等待当前DOM渲染完成后在继续执行,否则在获取最小高度的列时,每次获取到的高度都为0
this.mountMenu(index + 1)
})
}
},
selectCol() {
//获取当前高度最小的列
var getHeight = (ref) => {
return this.$refs[ref].offsetHeight
}
var height1 = getHeight('col1')
var height2 = getHeight('col2')
var height3 = getHeight('col3')
var height4 = getHeight('col4')
switch (Math.min(height1, height2, height3, height4)) {
case height1:
return 'dataList1'
break
case height2:
return 'dataList2'
break
case height3:
return 'dataList3'
case height4:
return 'dataList4'
break
}
}