在小编刚入行的时候,参考着度娘里的答案,用js实现过一次瀑布流布局。所以在接到瀑布流布局的设计稿时,第一反应就是利用js实现。
js实现
大概思路是:
css
-
- 瀑布流容器设置绝对定位,然后给所有子元素设置相对定位。
-
- 通过ajax请求拿到数据,并渲染到页面上
div class="waterfall-list-container" >
<div class="item-container" >
</div>
</div>
css
.waterfall-list-container {
position: relative;
.item-container {
position: absolute;
}
}
js
-
- 定义一个数组变量(数组长度和瀑布流布局所展示的列数相等),用于记录每一列的高度变化。
-
- 获取到所有瀑布流容器里的子元素,并遍历。
-
- 根据记录高度变化数组中的值,依次给每一列的子元素设置距离顶部的高度。并改变瀑布流容器的高度。
// 瀑布流容器
const parent = document.querySelector('.waterfall-list-container')
parent.style.height = `${0}px`
// 瀑布流当中的item
const waterfalls = document.querySelectorAll('.item-container')
if (!waterfalls.length) {
return
}
// 列数
const col = 2
// 间隔
const xspace = this.gutterWidth
const yspace = this.gutterWidth
const w = waterfalls[0].offsetWidth
// 一个数组,记录每一列距离顶部高度变化
const colHs = []
waterfalls.forEach((ele, index) => {
// 瀑布流容器高度
const parentH = parent.offsetHeight
// 对应的上一个元素的高度
const preDomH = index - col >=0 && waterfalls[index - col].offsetHeight
// 当前元素高度
const currentDomH = ele.offsetHeight
if (index < col) {
// 第一行比较特殊,距离顶部的距离都是0
colHs[index] = 0
} else {
// 记录每一列高度的变化
const i = index % col
preDomH && (colHs[i] += preDomH + yspace)
}
ele.style.top = `${colHs[index % col]}px`
ele.style.left = `${(index % col) * (w + xspace)}px`
let maxH = colHs[0]
colHs.forEach(h => {
+maxH < h && (maxH = h)
})
if (maxH) {
parent.style.height = `${+maxH + currentDomH}px`
} else {
parentH < currentDomH && (parent.style.height = `${currentDomH}px`)
}
})
自从用了这个方法之后,测试提给我的bug就没断过o(╥﹏╥)o,大致就是有些子元素重叠的问题。我猜测的原因是:因为小编所做项目的子元素中是带图的,所有应该是js执行的时候图片还未加载完成,导致高度计算错误。(如果不对,欢迎各位大佬指正!)
我思前想后,这不行啊。而且vue项目里面有操作dom元素的操作,也是糟糕了点。于是再次拜访了万能的度娘,找到了纯css方法实现了瀑布流的方法。
纯css实现
按照百度到的方法试了一下,果真方便快捷有效。就是:纯css布局出来的瀑布流是纵向排序,但是需求上要求是横向布局(我的天,和需求不符啊~ ~ ~ ~)
demo
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<style>
.masonry {
column-count: 2;
column-gap: 0;
}
.item {
break-inside: avoid;
box-sizing: border-box;
text-align: center;
line-height: 150px;
border: 1px solid skyblue;
}
</style>
<body>
<div class="masonry">
<div class="item i1">
<div class="item__content">
1
</div>
</div>
<div class="item i2">
<div class="item__content">
2
</div>
</div>
<div class="item i3">
<div class="item__content">
</div>
</div>
<div class="item i4">
3
<div class="item__content">
</div>
</div>
<div class="item i5">
4
<div class="item__content">
</div>
</div>
<div class="item i6">
5
<div class="item__content">
</div>
</div>
<div class="item i7">
6
<div class="item__content">
</div>
</div>
<div class="item i8">
7
<div class="item__content">
</div>
</div>
<div class="item i9">
8
<div class="item__content">
</div>
</div>
<div class="item i10">
9
<div class="item__content">
</div>
</div>
<!-- more items -->
</div>
</body>
</html>
效果图如下:
哎!这可如何是好呢?看着设计图都快愁秃了o(╥﹏╥)o,正愁着小编突然灵光乍现,瀑布流布局可以先分成想要的列数,然后设置列之间左右间距以及每一列中的子元素的上下间距。至于怎么分成不同的列,数据顺序还是从左到右的呢?前面也说了,小编的项目是用的vue框架,利用 % 运算很快就可以实现效果了。
其他方法
-
- 瀑布流父组件声明flex布局
-
- 渲染相应列数的子元素
-
- 渲染每一列的子元素 区分相同列方法(列表下标 % 列数 === 当前渲染列数下标)
<div class="container">
<!-- col:瀑布流的列数 -->
<div class="listl-container" v-for="(item, index) in col" :key="colIndex">
<div
v-for="(items, indexs) in list"
v-if="indexs % col === index"
:key="`wf-list-${index}`"
class="item">
<!-- you content -->
</div>
</div>
</div>
.container {
display: flex;
.list-container {
flex: 1;
margin: 0 10px;
&:first-of-type {
margin-right: 4px;
}
&:last-of-type {
margin-left: 4px;
}
}
}
其实这个方法也不是最佳的,要渲染col * list.length 次。然而目前还没有想出其他更好的方法,只能先用着这个了~