研究篇--瀑布流布局(JS实现)
写在开头的话:本文引用b站前端小夏老师的思路改进而来,以封装一个简单的vue组件来说明。
先说什么是瀑布流:
众所周知,传统的布局方式在实现多行多列,盒子高度不一致的情况下,布局是长这样的:
看到这,多多少少都会说一句,"怎么这么丑??"
而我们期望的理想型是这样的--瀑布流
那么,该如何去实现呢?你的选择又是什么?
实现的原理:
其实原理也很简单,没错,那就是 绝 ! 对 ! 定 ! 位 !
可是,一个重点的地方就是,我如何确定定位的left,top两项的值呢?如果我们搞定这两个参数,那么这个瀑布流就实现了。
我们来分析下,就假设一行三列,如下图:
我们可以根据自己设定的列数columns,以及每列间隔数gap,就能算出每列的宽度是多少
每列宽度cWidth=(盒子宽度tWidth-gap*(columns-1))/columns
根据每列宽度以及列间距gap,就能算出第一行的盒子的 left 跟 top 值
重点来了~~~
第二行要怎么确认呢?
所谓瀑布流,就是得每个盒子间间距是一样的,那么由这句话引出来的就是:
第二行开始,每一个盒子都是追加在高度"最低"的一列上,一直追加下去
加1-------
加1-------
加1-------
其中,要实现上面最关键的一个点就是,我们要实时地知道每列的高度,因为它一直在变化着。
因此我们每列添加完,就需要使用变量记录好每列的高度,在新追加的时候,判断最低的一列往上加则可!
再来说实现:
文章开头说过,此例子会以vue的组件形式来说明...
创建一个WaterFall的组件
<template>
<div class="water-fall__root">
<!--使用默认插槽,直接可以标签直接输入内容 -->
<slot></slot>
</div>
</template>
定义好组件要接收的参数props (盒子总宽度+列数+列间距)
props: {
// 控制瀑布布局的总宽度
tWidth: {
type: Number,
default: 800,
},
// 显示列数
columns: {
type: Number,
default: 3,
},
// 列间距
cGap: {
type: Number,
default: 10,
},
},
定义data数据,记录每列的总高度
data() {
return {
// 每列的总高
columnHeightArr: [],
};
},
开始过程的实现(methods方法内)
1.为了更好的控制随意传入项的样式,我们内部统一给它们加上一个特定类名
// 初始化添加内部控制类名
addOwnClassName(items) {
for (let i = 0; i < items.length; i++) {
items[i].classList.add('w-f-item');
}
},
2.开始计算每列宽度和设置每个盒子的宽度
// 计算每列宽度
getColumnWidth(tWidth, cGap, columns) {
return (tWidth - cGap * (columns - 1)) / columns;
},
// 设置每一个盒子的宽度
setColumnWidth(items, width) {
for (let i = 0; i < items.length; i++) {
items[i].style.width = width + 'px';
}
},
3.设置盒子排序(思路可以参考注释,也可以翻看上面原理部分)
// 设置列的排序
setColumnSort(items, cWidth) {
for (let i = 0; i < items.length; i++) {
// 先排第一行
// 设置top,left
if (i < this.columns) {
items[i].style.top = 0;
items[i].style.left = i * (cWidth + this.cGap) + 'px';
this.columnHeightArr.push(items[i].offsetHeight);
} else {
// 如果是>columns,就算第二行开始的
// 先判断哪列高度最低
const lowIndex = this.columnHeightArr.indexOf(
Math.min(...this.columnHeightArr)
);
// 得到left
items[i].style.left = lowIndex * (cWidth + this.cGap) + 'px';
// 得到top
items[i].style.top =
this.columnHeightArr[lowIndex] + this.cGap + 'px';
// 更新每列的高度
this.columnHeightArr[lowIndex] += this.cGap + items[i].offsetHeight;
}
}
},
4.然后定义一个更新的方法,用于集成调用上述定义的功能方法
// 更新,重新排列
updateFn() {
//每次更新重新置空一次高度数组,重新计算
this.columnHeightArr=[]
//获取父容器的dom
const rDom = document.querySelector('.water-fall__root');
//设置父容器的宽度
rDom.style.width = this.tWidth + 'px';
// 获取所有的子项
const items = document.querySelector('.water-fall__root').children;
// 每个子项都加上内部类名 w-f-item
this.addOwnClassName(items);
// 计算每列的宽度(总的宽度-全部间距)/列数
const cWidth = this.getColumnWidth(this.tWidth, this.cGap, this.columns);
// 设置每一个的宽度
this.setColumnWidth(items, cWidth);
// 设置列的排序(如果有图片,因图片高度需要加载之后才有,所以延时处理)
setTimeout(() => {
this.setColumnSort(items, cWidth);
}, 100);
},
5.选择在mounted钩子函数调用updateFn
mounted() {
this.updateFn();
},
样式部分
.water-fall__root {
position: relative;
.w-f-item {
position: absolute;
img {
width: 100%;
height: 100%;
}
}
}
最终--组件使用
<template>
<div>
<WaterFall :tWidth="1000" :columns="3" :cGap="5">
<!-- item内容 -->
<div v-for="item in imgArr" :key="item">
<img :src="item" alt="">
</div>
</WaterFall>
</div>
</template>
<script>
export default {
data() {
return {
imgArr:[
require('../src/assets/images/01.webp'),
require('../src/assets/images/02.webp'),
require('../src/assets/images/03.webp'),
require('../src/assets/images/04.webp'),
require('../src/assets/images/05.webp'),
require('../src/assets/images/06.webp'),
require('../src/assets/images/07.webp'),
require('../src/assets/images/08.webp'),
]
};
},
methods: {
},
};
</script>
<style></style>
完结 ! 再次感谢前端小夏老师提供的思路,也望各位同学都能得到感悟。