最近项目要实现一个瀑布流布局,结合网上的案例以及自己的理解实现了一版,特在此记录一下,有兴趣的同学欢迎一起探讨交流学习 (第一次写这种东西~~~),废话不多说,开整!!!!!
先上张图!!!
【必看】原理:
说一下我对瀑布流布局原理的理解,如上图,实现这种布局方式,主要有绝对定位方式 以及两列布局方式,原理都是通过判断每列高度差来做,重新处理数据,动态获取每列元素盒子高度,每次往盒子高度小的那列push数据,绝对定位方式,通过找到最小高度的列,设置元素的 left top值来做,但是这种方式有个缺点是,由于子元素绝对定位导致父元素高度塌陷,如果我们需要获取父元素高度做一些事情的话,可能不太好处理,所以我这里使用 float 两列布局(两列是一个概数,当然你可以设置5 6 7 8 列)
步骤一:
html css 直接上代码
<template>
<!-- 运用float 布局,多列数组方式,如果用绝对定位布局,会使父元素高度塌陷,使用不方便 -->
<div class="waterfall clearfix">
<div class="list" v-for="(columnList,i) in newList" :key="i" ref="waterfall">
<slot name="content" :item="columnList"></slot>
</div>
</div>
</template>
<style scoped lang="less">
.waterfall {
.list {
float: left;
&:last-child {
margin-right: 0 !important;
}
}
}
.clearfix::after{
content:'';
display:block;
clear:both;
}
</style>
步骤二:
样式处理好了后,就该我们处理数据了
首先加载瀑布流组件的时候,动态创建二维数组,并且给每列数组添加一条数据,(我觉得这样好理解,每列里有了数据,也就能获取到每列的dom元素,也就能获取到高度差)
export default {
name: 'waterfall',
props: {
list: {
type: Array,
default: []
},
marginRight: {
type: Number,
default: '0'
}
},
data() {
return {
column: 2,
newList: [],
}
},
created() {
// 循环列数,给每一列先添加一条数据,那两列就有高度了,也能利用高度差进行判断
if(this.list.length > 1) {
for(let i = 0; i< this.column;i++) {
// 此处需要用$set 创建二维数组
this.$set(this.newList,i,[])
this.newList[i][0] = this.list.shift()
}
}else {
this.$set(this.newList,0,[])
this.newList[0][0] = this.list.shift()
}
},
}
步骤三:
处理父组件传过来的数据,动态的push到两列数组中
init() {
let domHeight = [] // 存放高度得数组
let waterfallDom = this.$refs.waterfall
waterfallDom.forEach((item)=>{
domHeight.push(item.offsetHeight)
item.style.marginRight = this.marginRight
})
let minHeight = Math.min(...domHeight); // 获取到最小高度
let index = domHeight.findIndex(item => item === minHeight) // 获取到最小高度得下标
if(this.list.length) {
// 给最小高度得数组里添加数据
this.newList[index].push(this.list.shift())
}
}
步骤四:
每次调用步骤三中的方法 动态的处理父组件传过来的数据,我这里用的监听
watch: {
// 这里要用 $nextTick 元素渲染完成 再次调用方法重新获取高度 push数据
list(val) {
console.log(val,'增加了嘛');
this.$nextTick(()=>{
this.init()
})
}
},
完结:撒花!!!
奥! 完整代码附上!ps:这里没有处理图片,瀑布流最重要的是图片高度的自适应,由于图片加载有延迟,影响获取dom元素的高度,所以最完美的是服务端把图片高度返回,提前设置子元素图片高度占位,这样就完美了!!!!(完整代码组件可以直接拿来使用)
<waterfall :list="item.items" marginRight="9px">
<template #content="list">
<goods :obj="val" v-for="(val,s) in list.item" :key="s" ></goods>
</template>
</waterfall>
<template>
<!-- 运用float 布局,多列数组方式,如果用绝对定位布局,会使父元素高度塌陷,使用不方便 -->
<div class="waterfall clearfix">
<div class="list" v-for="(columnList,i) in newList" :key="i" ref="waterfall">
<slot name="content" :item="columnList"></slot>
</div>
</div>
</template>
<script>
export default {
name: 'waterfall',
props: {
list: {
type: Array,
default: []
},
marginRight: {
type: Number,
default: '0'
}
},
data() {
return {
column: 2,
newList: [],
}
},
created() {
// 循环列数,给每一列先添加一条数据,那两列就有高度了,也能利用高度差进行判断
// 这里写的不好,有需要的同学自己优化吧
if(this.list.length > 1) {
for(let i = 0; i< this.column;i++) {
// 此处需要用$set 创建二维数组
this.$set(this.newList,i,[])
this.newList[i][0] = this.list.shift()
}
}else {
this.$set(this.newList,0,[])
this.newList[0][0] = this.list.shift()
}
},
watch: {
// 这里要用 $nextTick 元素渲染完成 再次调用方法重新获取高度 push数据
list(val) {
// console.log(val,'增加了嘛');
this.$nextTick(()=>{
this.init()
})
}
},
methods: {
init() {
let domHeight = [] // 存放高度得数组
let waterfallDom = this.$refs.waterfall
waterfallDom.forEach((item)=>{
domHeight.push(item.offsetHeight)
item.style.marginRight = this.marginRight / 3.75+ 'vw'
})
let minHeight = Math.min(...domHeight); // 获取到最小高度
let index = domHeight.findIndex(item => item === minHeight) // 获取到最小高度得下标
if(this.list.length) {
// 给最小高度得数组里添加数据
this.newList[index].push(this.list.shift())
}
}
}
}
</script>
<style scoped lang="less">
.waterfall {
.list {
float: left;
margin-right: 9px;
&:last-child {
margin-right: 0 !important;
}
}
}
.clearfix::after{
content:'';
display:block;
clear:both;
}
</style>