1. 什么是瀑布流
瀑布流,又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。即多行等宽元素排列,后面的元素依次添加到其后,等宽不等高,根据图片原比例缩放直至宽度达到我们的要求,依次按照规则放入指定位置。
2. 为什么使用瀑布流
瀑布流布局在我们现在的前端页面中经常会用的到,它可以有效的降低页面的复杂度,节省很多的空间,对于整个页面不需要太多的操作,只需要下拉就可以浏览用户需要看到的数据;并且,瀑布流可以提供很好的用户体验,通过结合下拉刷新,上拉加载进行数据的懒加载等操作,对于用户的体验感来说是接近于满分的!
3. 瀑布流的特点
其实瀑布流的特点就是参差不齐的排列方式,以及流式布局的扩展性,可以通过界面展示给用户多条数据,并且让用户可以有向下浏览的冲动。
实现瀑布流的方式有很多种,本期主要讲解js实现瀑布流
首先创建一个主页面,里面可以进行数据交互,这里我用setTimeout表示每次请求的数据,new IntersectionObserver是dom出现在可视窗口就会执行,用来做懒加载
<template>
<div class="Falls_box">
<div class="header title">瀑布流</div>
<Falls ref="falls" @loadend="loading = false"></Falls>
<div class="loading">
<span v-show="loading">正在加载...</span>
<span v-show="!loading && !finish" ref="loadmore"
>点击加载更多</span
>
<span v-show="!loading && finish">没有更多数据</span>
</div>
</div>
</template>
<script>
import Falls from "./Falls.vue";
export default {
name: "Falls_box",
data() {
return {
loading: false,
finish: false,
page: 0,
imgList:[
{
id:1,
url:'https://gips3.baidu.com/it/u=871100818,1642628489&fm=3042&app=3042&f=JPEG&wm=1,huayi,0,0,13,9&wmo=0,0',
},{
id:2,
url:'https://gips2.baidu.com/it/u=2514616750,584354736&fm=3042&app=3042&f=JPEG&wm=1,huayi,0,0,13,9&wmo=0,0',
},{
id:3,
url:'https://gips0.baidu.com/it/u=3622901982,1201032983&fm=3042&app=3042&f=JPEG&wm=1,huayi,0,0,13,9&wmo=0,0',
},{
id:4,
url:'https://gips2.baidu.com/it/u=783874742,472799755&fm=3042&app=3042&f=JPEG&wm=1,huayi,0,0,13,9&wmo=0,0',
},{
id:5,
url:'https://gips0.baidu.com/it/u=1504464430,772353756&fm=3042&app=3042&f=JPEG&wm=1,huayi,0,0,13,9&wmo=0,0',
},{
id:6,
url:'https://gips1.baidu.com/it/u=120482431,301978690&fm=3042&app=3042&f=JPEG&wm=1,huayi,0,0,13,9&wmo=0,0',
}
]
};
},
mounted() {
new IntersectionObserver(
(entries, observer) => {
// 如果不是相交,则直接返回
if (!entries[0].intersectionRatio) return;
this.onLoadMore();
},
{ root: document.body }
).observe(this.$refs.loadmore);
},
// 挂载方法
methods: {
onLoadMore() {
if (this.finish || this.loading) return;
this.loading = true;
var sendData = {
page: ++this.page,
};
setTimeout(() => {
if(true){//true为继续加载
this.$refs.falls.push(list);
}else{
this.loading = false;
this.finish = true;
}
}, 1000);
},
},
// 计算属性
computed: {},
components: { Falls },
};
</script>
<style lang='less' scoped>
.Falls_box {
> .loading {
text-align: center;
margin: 10px 0;
font-size: 14px;
color: rgb(89, 89, 255);
}
}
</style>
看到我们在setTimeout中把数据添加到子页面
子页面对每次push进来的图片进行处理瀑布排列,这里的处理流程就是,首先创建一个数组poss用来表示每列的当前高度,然后对这个图片并且设置一个宽度,根据这个宽度拿到图片高度,假如当前屏宽除设置的瀑布流列宽有三列 poss的初始状态就为[0,0,0],
接下会使用循环对poss遍历找出三位数字中最低值的下标,这个下表就是我们接下来要添加图片的位置
<template>
<div class="Falls">
<img
v-for="v in list"
:key="v.id"
:src="v.url"
:style="{
width: v.style.width + 'px',
height: v.style.height + 'px',
top: v.style.top + 'px',
left: v.style.left + 'px',
}"
/>
</div>
</template>
<script>
export default {
name: "Falls",
data() {
return {
list: [],
wait_list: [],
width: 300,
num_x: 1,
gap_x: 0,
gap_y: 0,
poss: [],
loading: false,
};
},
created() {},
mounted() {
this.resize();
window.addEventListener("resize", this.resize);
},
beforeDestroy() {
this.wait_list = [];
window.removeEventListener("resize", this.resize);
},
// 挂载方法
methods: {
push(arr) {
this.wait_list = this.wait_list.concat(arr);
if (!this.loading) {
this.loading = true;
this.loadImg();
}
},
loadImg() {
if (this.wait_list.length <= 0) {
this.loading = false;
this.$emit("loadend");
return;
}
let url = this.wait_list[0].url;
let id = this.wait_list[0].id;
let img = document.createElement("img");
img.src = url;
img.style.width = this.width + "px";
img.style.position = "absolute";
img.style.top = "-99999px";
document.body.appendChild(img);
img.addEventListener("load", () => {
var idx = 0;
var pos_num = this.poss[idx];
for (var i = 1; i < this.poss.length; i++) {
if (pos_num > this.poss[i]) {
idx = i;
pos_num = this.poss[i];
}
}
let width = img.offsetWidth;
let height = img.offsetHeight;
let top = this.poss[idx] + this.gap_y;
let left = (idx + 1) * this.gap_x + idx * width;
this.poss[idx] = top + height;
let obj = {
id,
url,
style: { width, height, top, left },
};
this.list.push(obj);
img.remove();
this.wait_list.splice(0, 1);
this.$el.style.height =
Math.max(...this.poss) + this.gap_y + "px";
if (this.wait_list.length <= 0) {
this.loading = false;
this.$emit("loadend");
} else {
this.loadImg();
}
});
},
resize() {
var t_width = this.$el.offsetWidth;
var width = this.width;
var num_x = parseInt(t_width / (width + 20));
if (num_x < 1) num_x = 1;
var remain = t_width - width * num_x;
var gap_x = remain / (num_x + 1);
this.gap_x = gap_x;
this.gap_y = gap_x > 30 ? 30 : gap_x;
this.num_x = num_x;
var poss = [];
for (var i = 0; i < num_x; i++) poss.push(0);
this.poss = poss;
this.reset();
},
reset() {
for (let i = 0; i < this.list.length; i++) {
let width = this.list[i].style.width;
let height = this.list[i].style.height;
var idx = 0;
var pos_num = this.poss[idx];
for (var j = 1; j < this.poss.length; j++) {
if (pos_num > this.poss[j]) {
idx = j;
pos_num = this.poss[j];
}
}
let top = this.poss[idx] + this.gap_y;
let left = (idx + 1) * this.gap_x + idx * width;
this.poss[idx] = top + height;
this.list[i].style = { width, height, top, left };
}
this.$el.style.height = Math.max(...this.poss) + this.gap_y + "px";
},
},
// 计算属性
computed: {},
components: {},
};
</script>
<style lang='less' scoped>
.Falls {
width: 100%;
height: auto;
position: relative;
> img {
position: absolute;
transition: all 0.15s;
}
}
</style>
resize()方法则是监听屏幕宽度的变化进行修改边距和poss的列数具体效果如图