'瀑布流’是前端的一种’布局’方式,就像我们经常’搜百度’看到的’图片列表’都是’瀑布流’完成的,如下图,
‘瀑布流’会根据’某一列’的’高度’,来自动向’最低高度’的一’列’下面继续添加元素,下面是在’vue’项目中
使用’瀑布流’的案例。
vue2
<template>
<div class="box">
<div class="col" ref="col1">
<transition-group name="list">
<div class="item" v-for="item in dataList1" :key="item.id">
{{item.text}}
<img :src="item.url" />
</div>
</transition-group>
</div>
<div class="col" ref="col2">
<transition-group name="list">
<div class="item" v-for="item in dataList2" :key="item.id">
{{item.text}}
<img :src="item.url" />
</div>
</transition-group>
</div>
</div>
</template>
<script>
export default {
data() {
return {
mainMenuList: [
{
id: 1,
text: '我是1',
url: 'https://img.zcool.cn/community/01162e5d903ad6a8012060bee46bf3.jpg@1280w_1l_2o_100sh.jpg'
},
{
id: 2,
text: '我是2',
url: 'https://img.zcool.cn/community/0121a55d903ad6a801211d53066683.jpg@1280w_1l_2o_100sh.jpg'
},
{
id: 3,
text: '我是3',
url: 'http://pic39.nipic.com/20140321/18063302_210604412116_2.jpg'
},
{
id: 4,
text: '我是4',
url: 'https://img.zcool.cn/community/0121a55d903ad6a801211d53066683.jpg@1280w_1l_2o_100sh.jpg'
},
{
id: 5,
text: '我是5',
url: 'http://pic39.nipic.com/20140321/18063302_210604412116_2.jpg'
},
{
id: 6,
text: '我是6',
url: 'http://pic39.nipic.com/20140321/18063302_210604412116_2.jpg'
},
{
id: 7,
text: '我是7',
url: 'https://img.zcool.cn/community/0121a55d903ad6a801211d53066683.jpg@1280w_1l_2o_100sh.jpg'
},
],
dataList1: [],
dataList2: []
}
},
mounted() {
this.mountMenu()
},
methods: {
mountMenu(arg) {
var temp = this.mainMenuList
var index = arg || 0
var refName = this.selectCol()
if (temp.length > index) {
this[refName].push(this.mainMenuList[index])
this.$nextTick(() => {
this.mountMenu(index + 1)
})
}
},
selectCol() {
var getHeight = (ref) => {
return this.$refs[ref].offsetHeight
}
var height1 = getHeight('col1')
var height2 = getHeight('col2')
switch (Math.min(height1, height2)) { // Math.min()方法返回参数中最小的值
case height1:
return 'dataList1'
case height2:
return 'dataList2'
}
}
}
}
</script>
<style scoped>
.box{
overflow: hidden;
width: 400px;
}
.col{
float: left;
width: 100px;
}
img{
width: 100%;
}
</style>
vue3
<template>
<div class="box">
<div class="col" ref="col1">
<div class="item" v-for="item in dataList1" :key="item.id">
<img :src="item.url" loading="lazy" alt="" />
{{ item.text }}
</div>
</div>
<div class="col" ref="col2">
<div class="item" v-for="item in dataList2" :key="item.id">
<img :src="item.url" loading="lazy" alt="" />
{{ item.text }}
</div>
</div>
</div>
</template>
<script>
import { defineComponent, reactive, toRefs, onMounted, ref, nextTick } from "vue";
export default defineComponent({
name: "waterfall",
setup() {
const col1 = ref(null);
const col2 = ref(null);
const state = reactive({
mainMenuList: [
{
id: 1,
text: "我是1",
url: "https://img.zcool.cn/community/01162e5d903ad6a8012060bee46bf3.jpg@1280w_1l_2o_100sh.jpg"
},
{
id: 2,
text: "我是2",
url: "https://img.zcool.cn/community/0121a55d903ad6a801211d53066683.jpg@1280w_1l_2o_100sh.jpg"
},
{
id: 3,
text: "我是3",
url: "http://pic39.nipic.com/20140321/18063302_210604412116_2.jpg"
},
{
id: 4,
text: "我是4",
url: "https://img.zcool.cn/community/0121a55d903ad6a801211d53066683.jpg@1280w_1l_2o_100sh.jpg"
},
{
id: 5,
text: "我是5",
url: "http://pic39.nipic.com/20140321/18063302_210604412116_2.jpg"
},
{
id: 6,
text: "我是6",
url: "http://pic39.nipic.com/20140321/18063302_210604412116_2.jpg"
},
{
id: 7,
text: "我是7",
url: "https://img.zcool.cn/community/0121a55d903ad6a801211d53066683.jpg@1280w_1l_2o_100sh.jpg"
},
{
id: 8,
text: "我是8",
url: "https://img.zcool.cn/community/01162e5d903ad6a8012060bee46bf3.jpg@1280w_1l_2o_100sh.jpg"
},
{
id: 9,
text: "我是9",
url: "https://img.zcool.cn/community/0121a55d903ad6a801211d53066683.jpg@1280w_1l_2o_100sh.jpg"
},
{
id: 10,
text: "我是10",
url: "https://img0shdi28hu1.sunuping.com/upload/jpg/20230804095136/1687280090275311617.jpg"
},
{
id: 11,
text: "我是11",
url: "https://img.zcool.cn/community/0121a55d903ad6a801211d53066683.jpg@1280w_1l_2o_100sh.jpg"
},
{
id: 12,
text: "我是12",
url: "http://pic39.nipic.com/20140321/18063302_210604412116_2.jpg"
},
{
id: 13,
text: "我是13",
url: "http://pic39.nipic.com/20140321/18063302_210604412116_2.jpg"
},
{
id: 14,
text: "我是14",
url: "http://pic39.nipic.com/20140321/18063302_210604412116_2.jpg"
}
],
dataList1: [],
dataList2: []
});
onMounted(() => {
mountMenu();
});
const mountMenu = arg => {
let temp = state.mainMenuList;
let index = arg || 0;
let refName = selectCol();
if (temp.length > index) {
state[refName].push(state.mainMenuList[index]);
nextTick(() => {
mountMenu(index + 1);
});
}
};
const selectCol = () => {
let height1 = col1.value.offsetHeight;
let height2 = col2.value.offsetHeight;
switch (
Math.min(height1, height2) // Math.min()方法返回参数中最小的值
) {
case height1:
return "dataList1";
case height2:
return "dataList2";
}
};
return {
...toRefs(state),
col1,
col2
};
}
});
</script>
<style lang="less" scoped>
.box {
width: 375px;
}
.col {
float: left;
width: 185px;
}
img {
width: 100%;
object-fit: contain;
}
.item {
text-align: center;
margin: 10px 0;
}
</style>
两行
<template>
<div class="page-main">
<div class="card">
<div class="coloum1">
<div class="card-item" v-for="(item, index) in cardList1" :key="index" :style="[{ background: item.color }, { height: item.height }, { lineHeight: item.height }]" :class="{ visibles: isVisibility }">
<p class="text">{{ item.num }}</p>
</div>
</div>
<div class="coloum2">
<div class="card-item" v-for="(item, index) in cardList2" :key="index" :style="[{ background: item.color }, { height: item.height }, { lineHeight: item.height }]" :class="{ visibles: isVisibility }">
<p class="text">{{ item.num }}</p>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, reactive, nextTick } from "vue";
const cardList = reactive([
// 测试数据
{
num: "0",
color: "#FCCF0A",
height: "100px"
},
{
num: "1",
color: "#e53e26",
height: "200px"
},
{
num: "2",
color: "green",
height: "300px"
},
{
num: "3",
color: "red",
height: "100px"
},
{
num: "4",
color: "#000",
height: "200px"
},
{
num: "5",
color: "blue",
height: "300px"
},
{
num: "6",
color: "#691ba3",
height: "100px"
},
{
num: "7",
color: "orange",
height: "400px"
},
{
num: "8",
color: "yellow",
height: "500px"
},
{
num: "9",
color: "#409eff",
height: "100px"
}
]);
const isVisibility = ref(true);
// 由于渲染时候对数据的两次赋值,则会出现一次闪现,需要防抖
onMounted(() => {
equallyCard();
nextTick(() => {
caLFlex();
}).then(() => {
isVisibility.value = true;
});
});
const cardList1 = ref([]); // 各列的数据
const cardList2 = ref([]);
function equallyCard() {
// 平分数据,确保页面上遍历卡片dom的真实顺序与平分的一致 document.querySelectorAll('.card-item'))
let num = parseInt(cardList.length / 2);
cardList.forEach((item, index) => {
if (index < num) {
cardList1.value.push(item);
return;
}
cardList2.value.push(item);
});
}
function caLFlex() {
let arr1 = []; // 第一列的值
let arr2 = []; // 第二列的值
let heightArry_1 = []; // 第一列的卡片高度
let heightArry_2 = []; // 第二列的卡片高度
Array.from(document.querySelectorAll(".card-item")).forEach((item, index) => {
if (index === 0) {
// 第一行中的元素无需判断,直接加到新的数组中
heightArry_1.push(item.offsetHeight);
arr1.push(cardList[index]);
return;
}
if (index === 1) {
heightArry_2.push(item.offsetHeight);
arr2.push(cardList[index]);
return;
}
const heightTotal_1 = heightArry_1.length ? Array.from(heightArry_1).reduce((acc, cur) => acc + cur) : 0; // 第一列的总高度
const heightTotal_2 = heightArry_2.length ? Array.from(heightArry_2).reduce((acc, cur) => acc + cur) : 0; // 第二列的总高
// 找到最小值
let minNumber = Math.min(heightTotal_1, heightTotal_2);
switch (minNumber) {
case heightTotal_1:
heightArry_1.push(item.offsetHeight);
arr1.push(cardList[index]);
break;
case heightTotal_2:
heightArry_2.push(item.offsetHeight);
arr2.push(cardList[index]);
break;
}
});
// 重新将数据赋值给各列数组
cardList1.value = arr1;
cardList2.value = arr2;
}
</script>
<style lang="less" scoped>
.page-main {
background: #ffffff;
height: 100vh;
overflow: auto;
// padding: 0 30px;
.card {
display: flex;
flex-direction: row;
justify-content: space-around;
.card-item {
visibility: hidden;
margin-bottom: 20px;
text-align: center;
width: 187.5px;
border-radius: 16px;
}
.visibles {
visibility: visible;
}
}
}
</style>
三行
<template>
<div class="page-main">
<div class="card">
<div class="coloum1">
<div class="card-item" v-for="(item, index) in cardList1" :key="index" :style="[{ background: item.color }, { height: item.height }, { lineHeight: item.height }]" :class="{ visibles: isVisibility }">
<p class="text">{{ item.num }}</p>
</div>
</div>
<div class="coloum2">
<div class="card-item" v-for="(item, index) in cardList2" :key="index" :style="[{ background: item.color }, { height: item.height }, { lineHeight: item.height }]" :class="{ visibles: isVisibility }">
<p class="text">{{ item.num }}</p>
</div>
</div>
<div class="coloum3">
<div class="card-item" v-for="(item, index) in cardList3" :key="index" :style="[{ background: item.color }, { height: item.height }, { lineHeight: item.height }]" :class="{ visibles: isVisibility }">
<p class="text">{{ item.num }}</p>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, reactive, nextTick } from "vue";
const cardList = reactive([
// 测试数据
{
num: "0",
color: "#FCCF0A",
height: "100px"
},
{
num: "1",
color: "#e53e26",
height: "200px"
},
{
num: "2",
color: "green",
height: "300px"
},
{
num: "3",
color: "red",
height: "100px"
},
{
num: "4",
color: "#000",
height: "200px"
},
{
num: "5",
color: "blue",
height: "300px"
},
{
num: "6",
color: "#691ba3",
height: "100px"
},
{
num: "7",
color: "orange",
height: "400px"
},
{
num: "8",
color: "yellow",
height: "500px"
},
{
num: "9",
color: "#409eff",
height: "100px"
}
]);
const isVisibility = ref(true);
// 由于渲染时候对数据的两次赋值,则会出现一次闪现,需要防抖
onMounted(() => {
equallyCard();
nextTick(() => {
caLFlex();
}).then(() => {
isVisibility.value = true;
});
});
const cardList1 = ref([]); // 各列的数据
const cardList2 = ref([]);
const cardList3 = ref([]);
function equallyCard() {
// 平分数据,确保页面上遍历卡片dom的真实顺序与平分的一致 document.querySelectorAll('.card-item'))
let num = parseInt(cardList.length / 3);
cardList.forEach((item, index) => {
if (index < num) {
cardList1.value.push(item);
return;
}
if (index < 2 * num) {
cardList2.value.push(item);
return;
}
cardList3.value.push(item);
});
}
function caLFlex() {
let arr1 = []; // 第一列的值
let arr2 = []; // 第二列的值
let arr3 = []; // 第二列的值
let heightArry_1 = []; // 第一列的卡片高度
let heightArry_2 = []; // 第二列的卡片高度
let heightArry_3 = []; // 第二列的卡片高度
Array.from(document.querySelectorAll(".card-item")).forEach((item, index) => {
if (index === 0) {
// 第一行中的元素无需判断,直接加到新的数组中
heightArry_1.push(item.offsetHeight);
arr1.push(cardList[index]);
return;
}
if (index === 1) {
heightArry_2.push(item.offsetHeight);
arr2.push(cardList[index]);
return;
}
if (index === 2) {
heightArry_3.push(item.offsetHeight);
arr3.push(cardList[index]);
return;
}
const heightTotal_1 = heightArry_1.length ? Array.from(heightArry_1).reduce((acc, cur) => acc + cur) : 0; // 第一列的总高度
const heightTotal_2 = heightArry_2.length ? Array.from(heightArry_2).reduce((acc, cur) => acc + cur) : 0; // 第二列的总高
const heightTotal_3 = heightArry_3.length ? Array.from(heightArry_3).reduce((acc, cur) => acc + cur) : 0; // 第三列的总高度
// 找到最小值
let minNumber = Math.min(heightTotal_1, heightTotal_2, heightTotal_3);
switch (minNumber) {
case heightTotal_1:
heightArry_1.push(item.offsetHeight);
arr1.push(cardList[index]);
break;
case heightTotal_2:
heightArry_2.push(item.offsetHeight);
arr2.push(cardList[index]);
break;
case heightTotal_3:
heightArry_3.push(item.offsetHeight);
arr3.push(cardList[index]);
break;
}
});
// 重新将数据赋值给各列数组
cardList1.value = arr1;
cardList2.value = arr2;
cardList3.value = arr3;
}
</script>
<style lang="less" scoped>
.page-main {
background: #ffffff;
height: 100vh;
overflow: auto;
// padding: 0 30px;
.card {
display: flex;
flex-direction: row;
justify-content: space-around;
.card-item {
visibility: hidden;
margin-bottom: 20px;
text-align: center;
width: 125px;
border-radius: 16px;
}
.visibles {
visibility: visible;
}
}
}
</style>