思路
- 页面 pege 分成左浮动的数列 lineBox,每列中图片 sinImg 依次向下摆放
- 每加载一张图片时,判断页面中哪列的高度最小,将当前图片放到最小的那列尾部
- 监听当前图片 onload 事件,当前图片加载完成后,再加载下一张图片
- 等待上一张图片加载完的事件,可以用递归完成
实现
初步搭建瀑布流
<div class="page">
<div class="floatBox clearfix" ref="floatBox">
<div
class="lineBox"
v-for="(item, index) in imgList"
:key="index"
ref="lineBox"
>
<div
class="sinImg"
v-for="(itemImg, indexImg) in item"
:key="indexImg"
ref="sinImg"
>
<img :src="itemImg.url" alt="" />
<div class="desc">{{ itemImg.desc }}</div>
</div>
</div>
</div>
</div>
<style lang="scss" scoped>
.page {
height: 100%;
.floatBox {
background: #f5f5f5;
padding: 20px;
height: 100%;
box-sizing: border-box;
overflow-y: auto;
.lineBox {
float: left;
margin-right: 10px;
&:last-child {
margin-right: 0;
}
.sinImg {
width: 200px;
margin-bottom: 10px;
background: #fff;
border-radius: 8px;
overflow: hidden;
img {
width: 100%;
height: auto;
}
.desc {
width: 100%;
line-height: 30px;
padding: 10px;
box-sizing: border-box;
}
}
}
}
}
.clearfix:after {
content: "";
display: block;
clear: both;
visibility: hidden;
}
</style>
data() {
return {
randomImg: [
require("@I/float1.png"),
require("@I/float2.png"),
require("@I/float3.png"),
require("@I/float4.png"),
require("@I/float5.png"),
require("@I/float6.png"),
],
randomDesc: [
"描述信息,描述信息描述信息,描述信息。",
"描述信息描述信息,描述信息,描述信息描述信息描述信息描述信息描述信息。",
"描述信息描述信息描述信息。",
"描述信息,描述信息描述信息,描述信息描述信息描述信息,描述信息描述信息描述信息描述信息描述信息描述信息。",
"描述信息描述信息描述信息,描述信息描述信息描述信息描述信息描述信息。",
"描述信息描述信息,描述信息描述信息描述信息描述信息描述信息。",
],
imgList: [],
split: 3,
};
},
mounted() {
this.initImg();
},
methods: {
initImg() {
this.getImgList(this.split).then((res) => {
for (var i = 0; i < this.split; i++) {
this.imgList[i] = [res[i]];
}
this.$forceUpdate();
});
},
getImgList(pageSize) {
return new Promise((resolve, reject) => {
let imgList = [];
for (var i = 0; i < pageSize; i++) {
let random1 = Math.floor(Math.random() * (0 - 6) + 6);
let random2 = Math.floor(Math.random() * (0 - 6) + 6);
imgList.push({
url: this.randomImg[random1],
desc: this.randomDesc[random2],
});
}
setTimeout(() => {
resolve(imgList);
}, 500);
});
},
},
![在这里插入图片描述](https://img-blog.csdnimg.cn/26d3ee01f65a46919b31992efedf31f8.png#pic_center)
判断列高添加图片
- 遍历数据返回结果数组
- 判断当前页面中,所有列盒子 lineBox 的高度,选取最低的那一列,将当前遍历的数据结果添加到此列
- 遍历时,使用定时器 setTimeout,相当于将执行函数按顺序放到队列中,后续会按照此顺序依次执行
initImg() {
this.getImgList(this.split).then((res) => {
for (var i = 0; i < this.split; i++) {
this.imgList[i] = [res[i]];
}
this.$forceUpdate();
this.loadMoreImg();
});
},
loadMoreImg() {
this.getImgList(20).then((res) => {
res.forEach((item, index) => {
setTimeout(() => {
this.pushImg(item);
}, 0);
});
});
},
pushImg(item) {
let lineBoxArr = this.$refs.lineBox;
let minBoxIndex = 0;
let minBox = 0;
lineBoxArr.forEach((item, index) => {
if (!minBox) {
minBox = item.offsetHeight;
minBoxIndex = index;
} else if (item.offsetHeight < minBox) {
minBox = item.offsetHeight;
minBoxIndex = index;
}
});
this.imgList[minBoxIndex].push(item);
this.$forceUpdate();
},
![在这里插入图片描述](https://img-blog.csdnimg.cn/acd66fc3f8e744358091bfe1f1e9b905.png#pic_center)
等待每张图片的 onload 事件
- 上述方法,队列在依次执行的时候,如果图片较大或者网络环境较差,可能会发生错列的情况。即在上一张图片未完全加载完成时,下一张图片加载前判断列高度最小值不准确的情况。
![在这里插入图片描述](https://img-blog.csdnimg.cn/f430b7e988324adeba903faaf06e5dd4.png#pic_center)
- 加入递归思路,请求到结果后,将结果数组赋值给一个递归数组 circleImgList
- 递归方法中,每次将 circleImgList 的第一项加入到最小列中,并且等待当前 item 的图片加载完后,移除递归数组 circleImgList 的第一项,再调用下次方法,直到递归数组被移除成空数组
circleFunc() {
if (this.circleImgList && this.circleImgList.length > 0) {
let lineBoxArr = this.$refs.lineBox;
let minBoxIndex = 0;
let minBox = 0;
lineBoxArr.forEach((item, index) => {
if (!minBox) {
minBox = item.offsetHeight;
minBoxIndex = index;
} else if (item.offsetHeight < minBox) {
minBox = item.offsetHeight;
minBoxIndex = index;
}
});
this.imgList[minBoxIndex].push(this.circleImgList[0]);
this.$forceUpdate();
this.$nextTick(() => {
let imgNode = lineBoxArr[minBoxIndex].lastChild.children[0];
imgNode.onload = () => {
this.consoleTimeStr("当前图片加载完了,进行下一次", "blue");
this.circleImgList.shift();
this.circleFunc();
};
});
}
},
consoleTimeStr(flag, color) {
let time = new Date();
let minute = time.getMinutes();
let second = time.getSeconds();
let millSecond = time.getMilliseconds();
if (minute < 10) {
minute = "0" + minute;
}
if (second < 10) {
second = "0" + second;
}
let str = minute + ":" + second + ":" + millSecond;
console.log("%c" + flag + ",当前时间:" + str, "color: " + color + ";");
},
![在这里插入图片描述](https://img-blog.csdnimg.cn/2db92e6816f548f19e1302a133dda889.png#pic_center)
完善
- 监听瀑布流盒子 floatBox 的滚动事件,当滚动到底部时,加载下一次数据
- 添加一个 loading 开关,等待当前数据加载完毕,再执行滚动加载事件
![在这里插入图片描述](https://img-blog.csdnimg.cn/9b3c798dd7144b909bcb087510f26ee7.png#pic_center)
<template>
<div class="page">
<div class="floatBox clearfix" ref="floatBox">
<div
class="lineBox"
v-for="(item, index) in imgList"
:key="index"
ref="lineBox"
>
<div
class="sinImg"
v-for="(itemImg, indexImg) in item"
:key="indexImg"
ref="sinImg"
>
<img :src="itemImg.url" alt="" />
<div class="desc">{{ itemImg.desc }}</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
randomImg: [
require("@I/float1.png"),
require("@I/float2.png"),
require("@I/float3.png"),
require("@I/float4.png"),
require("@I/float5.png"),
require("@I/float6.png"),
],
randomDesc: [
"描述信息,描述信息描述信息,描述信息。",
"描述信息描述信息,描述信息,描述信息描述信息描述信息描述信息描述信息。",
"描述信息描述信息描述信息。",
"描述信息,描述信息描述信息,描述信息描述信息描述信息,描述信息描述信息描述信息描述信息描述信息描述信息。",
"描述信息描述信息描述信息,描述信息描述信息描述信息描述信息描述信息。",
"描述信息描述信息,描述信息描述信息描述信息描述信息描述信息。",
],
imgList: [],
split: 3,
circleImgList: [],
timmer: null,
loading: false,
};
},
mounted() {
this.initImg();
},
methods: {
initImg() {
this.getImgList(this.split).then((res) => {
for (var i = 0; i < this.split; i++) {
this.imgList[i] = [res[i]];
}
this.$forceUpdate();
let floatBox = this.$refs.floatBox;
floatBox.addEventListener("scroll", (e) => {
this.scrollFunc();
});
this.loadMoreImg();
});
},
loadMoreImg() {
this.loading = true;
this.getImgList(20).then((res) => {
this.circleImgList = JSON.parse(JSON.stringify(res));
this.circleFunc();
});
},
getImgList(pageSize) {
return new Promise((resolve, reject) => {
let imgList = [];
for (var i = 0; i < pageSize; i++) {
let random1 = Math.floor(Math.random() * (0 - 6) + 6);
let random2 = Math.floor(Math.random() * (0 - 6) + 6);
imgList.push({
url: this.randomImg[random1],
desc: this.randomDesc[random2],
});
}
setTimeout(() => {
resolve(imgList);
}, 500);
});
},
scrollFunc() {
this.throttle(() => {
let floatBox = this.$refs.floatBox;
let scrollTop = floatBox.scrollTop;
let clientHeight = floatBox.clientHeight;
let scrollHeight = floatBox.scrollHeight;
if (scrollTop * 1 + clientHeight * 1 >= scrollHeight) {
this.consoleTimeStr("滚动到底部了", "red");
if (!this.loading) {
this.loadMoreImg();
}
}
this.timmer = null;
}, 200);
},
throttle(func, delay) {
return (() => {
if (this.timmer) {
return;
} else {
this.timmer = setTimeout(func, delay);
}
})();
},
pushImg(item) {
let lineBoxArr = this.$refs.lineBox;
let minBoxIndex = 0;
let minBox = 0;
lineBoxArr.forEach((item, index) => {
if (!minBox) {
minBox = item.offsetHeight;
minBoxIndex = index;
} else if (item.offsetHeight < minBox) {
minBox = item.offsetHeight;
minBoxIndex = index;
}
});
this.imgList[minBoxIndex].push(item);
this.$forceUpdate();
},
circleFunc() {
if (this.circleImgList && this.circleImgList.length > 0) {
let lineBoxArr = this.$refs.lineBox;
let minBoxIndex = 0;
let minBox = 0;
lineBoxArr.forEach((item, index) => {
if (!minBox) {
minBox = item.offsetHeight;
minBoxIndex = index;
} else if (item.offsetHeight < minBox) {
minBox = item.offsetHeight;
minBoxIndex = index;
}
});
this.imgList[minBoxIndex].push(this.circleImgList[0]);
this.$forceUpdate();
this.$nextTick(() => {
let imgNode = lineBoxArr[minBoxIndex].lastChild.children[0];
imgNode.onload = () => {
this.consoleTimeStr("当前图片加载完了,进行下一次", "blue");
this.circleImgList.shift();
this.circleFunc();
};
});
} else {
this.loading = false;
}
},
consoleTimeStr(flag, color) {
let time = new Date();
let minute = time.getMinutes();
let second = time.getSeconds();
let millSecond = time.getMilliseconds();
if (minute < 10) {
minute = "0" + minute;
}
if (second < 10) {
second = "0" + second;
}
let str = minute + ":" + second + ":" + millSecond;
console.log("%c" + flag + ",当前时间:" + str, "color: " + color + ";");
},
},
};
</script>
<style lang="scss" scoped>
.page {
height: 100%;
.floatBox {
background: #f5f5f5;
padding: 20px;
height: 100%;
box-sizing: border-box;
overflow-y: auto;
.lineBox {
float: left;
margin-right: 10px;
&:last-child {
margin-right: 0;
}
.sinImg {
width: 200px;
margin-bottom: 10px;
background: #fff;
border-radius: 8px;
overflow: hidden;
img {
width: 100%;
height: auto;
}
.desc {
width: 100%;
line-height: 30px;
padding: 10px;
box-sizing: border-box;
}
}
}
}
}
.clearfix:after {
content: "";
display: block;
clear: both;
visibility: hidden;
}
</style>