瀑布流
定义
瀑布流是一种页面布局,有多个宽度相同高度不同模块组成;能够自动地适应,达到一行一行展示的效果。 视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。这种瀑布流多见于图片为主的网站。
为什么使用
- 由于多栏分布,可以更好地适应移动端;
- 可以打破常规的页面布局,有良好的视觉体验;
- 参差不齐布局,有利调起下划的好奇心,吸引用户;
实现方式
- 方法一:通过css3的多列布局中的columns-count 来实现瀑布流;最为简单的实现方法,但这种布局为竖向排序,当需要滚动加载时,弊端就显现出来了。效果预览
- 方法二:通过js获取父级元素和子级元素的宽度和高度,子集元素进行遍历来确定当前元素所在位置;从而可以使用css的position进行定位; 具体的细节可看效果预览
- 方法三:同样也需要js来获取元素 使用grid的栅格布局中的grid-row-end,从而可达到效果;具体的细节可看效果展示 注:具体的实现方式可根据下面代码注释走
方法一
html
<div class="root">
<div class="item">
<img src="https://picsum.photos/450/325?image=100" alt="">
</div>
<div class="item">
<img src="https://picsum.photos/450/450?image=200" alt="">
</div>
<div class="item">
<img src="https://picsum.photos/450/280?image=300" alt="">
</div>
<div class="item">
<img src="https://picsum.photos/450/540?image=400" alt="">
</div>
<div class="item">
<img src="https://picsum.photos/450/380?image=500" alt="">
</div>
<div class="item">
<img src="https://picsum.photos/450/300?image=600" alt="">
</div>
<div class="item">
<img src="https://picsum.photos/450/400?image=700" alt="">
</div>
</div>
css
.root {
columns-count: 4;
column-gap: 10px;
}
img {
width: 100%;
border-radius:10px;
margin-bottom:10px;
}
@media only screen and (max-width: 768px) {
.root {
columns: 1;
}
}
@media only screen and (max-width: 992px) and (min-width: 768px) {
.root {
columns: 3;
}
}
@media only screen and (max-width: 1200px) and (min-width: 992px) {
.root {
columns: 4;
}
}
@media only screen and (min-width: 1200px) {
.root {
columns: 6;
}
}
方法二
html
<div>
<div id="root">
</div>
</div>
css
#root {
position: relative;
opacity: 0;
}
.item {
position: absolute;
}
.item>img {
width: 200px;
vertical-align: middle;
}
js
const imgList = [
"https://picsum.photos/450/325?image=10",
"https://picsum.photos/450/540?image=11",
"https://picsum.photos/450/600?image=12",
"https://picsum.photos/450/325?image=13",
"https://picsum.photos/450/540?image=14",
"https://picsum.photos/450/325?image=15",
"https://picsum.photos/450/325?image=16",
"https://picsum.photos/450/325?image=17",
"https://picsum.photos/450/540?image=18",
"https://picsum.photos/450/325?image=19",
"https://picsum.photos/450/605?image=20",
"https://picsum.photos/450/540?image=21"
]
let itemAll = '' // 所有的子节点
for (let index = 0; index < imgList.length; index++) {
itemAll = itemAll + `<div class="item">
<img src=${imgList[index]} alt="">
</div>`
}
// 函数防抖
const debounce = (fn) => {
let timeOut = null;
return (e) => {
clearTimeout(timeOut)
timeOut = setTimeout(() => {
fn()
}, 500)
}
}
// 设图片的宽度为200px
const imgWidth = 200
// 父节点
const rootDom = document.getElementById('root')
// 添加子节点
rootDom.innerHTML = itemAll
// 等图片加载完执行
window.addEventListener("load", () => waterFall())
window.addEventListener("resize", debounce(()=>waterFall()))
// 函数
const waterFall = () => {
console.log(234);
rootDom.style.opacity=1
// 动态的获取父节点的宽
const rootWidth = rootDom.offsetWidth
// 获取可以排对的列数
const columnNum = Math.floor(rootWidth / imgWidth)
// 获取子节点
const itemDom = document.getElementsByClassName('item')
// 高度数组
let heightArr = []
// 子元素进行遍历
for (let index = 0; index < itemDom.length; index++) {
// 当前的元素
const element = itemDom[index]
const elementHeight = element.offsetHeight
// 判断是否是第一行 下标小于列数是第一行
if (index < columnNum) {
heightArr.push(elementHeight)
element.style.left = index * imgWidth + 'px';
element.style.top = 0 + 'px';
} else {
const minIndex = minBox(heightArr)
const minValue = heightArr[minIndex]
element.style.left = minIndex * imgWidth + 'px';
element.style.top = minValue + 'px';
heightArr[minIndex] += elementHeight;
}
}
}
function minBox(box) {
var j = 0;
for (i in box) {
if (box[j] > box[i]) j = i
}
return j;
}
方法三
htnml
<div class="box">
<div id="root">
</div>
</div>
css
.box {
max-width: 960px;
margin-right: auto;
margin-left: auto;
}
#root {
display: grid;
grid-template-columns: repeat(1, minmax(100px, 1fr));
grid-gap: 10px;
grid-auto-rows: 0;
opacity: 0;
}
.item {
border-radius: 10px;
border: 1px solid #000;
overflow: hidden;
}
.item>img {
max-width: 100%;
vertical-align: middle;
}
@media only screen and (max-width: 1023px) and (min-width: 768px) {
#root {
grid-template-columns: repeat(2, minmax(100px, 1fr));
}
}
@media only screen and (min-width: 1024px) {
#root {
grid-template-columns: repeat(3, minmax(100px, 1fr));
}
}
js
const imgList = [
"https://picsum.photos/450/325?image=10",
"https://picsum.photos/450/540?image=11",
"https://picsum.photos/450/600?image=12",
"https://picsum.photos/450/325?image=13",
"https://picsum.photos/450/540?image=14",
"https://picsum.photos/450/325?image=15",
"https://picsum.photos/450/325?image=16",
"https://picsum.photos/450/325?image=17",
"https://picsum.photos/450/540?image=18",
"https://picsum.photos/450/325?image=19",
"https://picsum.photos/450/605?image=20",
"https://picsum.photos/450/540?image=21"
]
let itemAll = '' // 所有的子节点
for (let index = 0; index < imgList.length; index++) {
itemAll = itemAll + `<div class="item">
<img src=${imgList[index]} alt="">
</div>`
}
// 父节点
const rootDom = document.getElementById('root')
// 添加子节点
rootDom.innerHTML = itemAll
// 主函数
function resizeAllMasonryItems() {
const rootDom = document.getElementById('root')
rootDom.style.opacity = 1
const itemDom = document.getElementsByClassName('item')
if (itemDom && rootDom) {
for (var i = 0; i < itemDom.length; i++) {
const curDom = itemDom[i]
// 当前的元素
var rowGap = parseInt(window.getComputedStyle(rootDom).getPropertyValue('grid-row-gap'))
// 获取css中 grid-auto-rows 的值
var rowHeight = parseInt(window.getComputedStyle(rootDom).getPropertyValue('grid-auto-rows'))
// 获取当前节点下的img
var gridImagesAsContent = curDom.querySelector('img');
// 获取 模块所在位置 rowHeight没设置时为0
var rowSpan = Math.ceil((gridImagesAsContent.offsetHeight + rowGap) / (rowHeight + rowGap));
// 最为关键 grid-row-end
curDom.style.gridRowEnd = 'span ' + rowSpan;
}
}
}
window.addEventListener("load", resizeAllMasonryItems)
总结
方法一虽是最简单的,但方法二是最常用到的 ;方法三相对比方法二简单,但存在着向上取整,导致数值有一定的偏差;还是结合业务进行选择。
注: