瀑布流布局在业务中很常见,解决方案也有比较多,今天给大家介绍一种基于dom高度自动计算的实现方案
先看效果图
我们的列表是由图片和文字组成,一般的,图片是确定高度的(就算不清楚我们也可以临时加载然后判断),此处的文本内容要求是必须全部展示出来,不做切断处理,因此可能存在文本较少的情况,此时的卡片布局会相对的减少
我们将列表分为左右两列,用来单独放置卡片
<template>
<div class="WaterfallList">
<!-- 左侧列表 -->
<div class="WaterfallList-left">
<div ref="left">
<div class="card" v-for="(item, $index) in left" :key="$index">
<div
class="pic"
:style="{
height: item.height + 'px',
lineHeight: item.height + 'px',
}"
>
{{ item.index }}
</div>
<div class="title">{{ item.title }}</div>
</div>
</div>
</div>
<!-- 右侧列表 -->
<div class="WaterfallList-right">
<div ref="right">
<div class="card" v-for="(item, $index) in right" :key="$index">
<div
class="pic"
:style="{
height: item.height + 'px',
lineHeight: item.height + 'px',
}"
>
{{ item.index }}
</div>
<div class="title">{{ item.title }}</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
list: Array,
},
data() {
return {
left: [],
right: [],
};
}
};
</script>
因此,在渲染的时候我们需要获得当前左右列表的高度,然后将新的数据塞入到较小的那个容器之中
const newItem; // 下一个要插入的数据
let leftHeight = this.$refs.left.getBoundingClientRect().height;
let rightHeight = this.$refs.right.getBoundingClientRect().height;
let index = this.left.length + this.right.length;
if (leftHeight < rightHeight) {
this.left.push(newItem);
} else {
this.right.push(newItem);
}
由于使用了vue框架,每次进行数据变动后,dom并不一定会马上做出新的改变,因此可能导致上面的获取高度的代码并不能获取到渲染完成后的,因此,我们需要等待dom完成渲染, 通过如下代码实现
await this.$nextTick()
这样,我们在每次list放生改变的时候,就需要将新增的内容按情况推进我们的左右两个数组中
epxort default {
watch: {
list() {
this.update();
},
},
mounted() {
this.update();
},
methods: {
async update() {
while (this.left.length + this.right.length !== this.list.length) {
let leftHeight = this.$refs.left.getBoundingClientRect().height;
let rightHeight = this.$refs.right.getBoundingClientRect().height;
let index = this.left.length + this.right.length;
if (leftHeight < rightHeight) {
this.left.push(this.list[index]);
} else {
this.right.push(this.list[index]);
}
await this.$nextTick();
}
},
}
}
考虑到如果watch频繁触发会导致 update 方法多次调用,而此时前一次 update 工作并不一定完成,因此我们加一个锁来保证它不会重入
if (this.called) {
return;
}
this.called = true;
while (this.left.length + this.right.length !== this.list.length) {
let leftHeight = this.$refs.left.getBoundingClientRect().height;
let rightHeight = this.$refs.right.getBoundingClientRect().height;
let index = this.left.length + this.right.length;
if (leftHeight < rightHeight) {
this.left.push(this.list[index]);
} else {
this.right.push(this.list[index]);
}
await this.$nextTick();
}
this.called = false;
下面是完整的项目代码 waterfallList-calc-by-$nexttick - CodeSandbox
那么问题来了,如果列表发生删减怎么操作呢,我们可以
- 先把已经不存在 list 中的元素,从 left 和 right 中过滤出去
- 过滤完后,左右的高度可能差别很大,我们做一个循环,每次从较大的拿一列中取出最后一项,放到较小的里面,直到较大的那列不在大于较小的那列(移动过程中高度会发生变化)
- 此时继续上面的循环,将新增的内容按情况插入到左右两个