vue实现简易瀑布流
实现效果
1、不同屏幕尺寸下根据可视区域宽度判断 应该 放几列,这里用onresize演示
2、鼠标滚动到已加载数据的底部时再次请求数据,重新计算哪一列高度最小,push到最小的那一列
实现思路
1、瀑布流的盒子使用flex布局,每一列column的间距相等
2、获取数据,根据可视区域的宽度判断需要几列
3、假设根据判断需要n列,那么将获取到的数据前n个一次push到每一列,成为第一个元素
4、此时每一列都有了元素,找到高度最小的那一列,这里可能有多列,找到索引最小那列,然后将元素插入
5、监听滚动事件,可以是document,也可以是具体的元素,只要元素是包裹着瀑布流的并且overflow不是hidden;当滚动到底部时,请求数据,将获得的数据插入到已有的瀑布流中
具体代码
1、瀑布流的盒子使用flex布局,每一列column的间距相等
<div class="container"> <div class="column" v-for="(column,index) in columns" :key="index"> <div class="item" v-for="(item,i) in column.columnArr" :key="i" :style="{ height: item.height + 'px', background: item.background }" > {{ item.text }} </div> </div> </div>
.container { margin: 0 auto; padding-bottom: 20px; display: flex; justify-content: space-around; background: pink; transition: all 0.5s ease-in-out; } .item { width: 120px; color: #000; margin-top: 20px; display: flex; justify-content: center; align-items: center; transition: all 0.5s ease-in-out; }
2、获取数据,根据可视区域的宽度判断需要几列
// 根据可视区域的宽度判断需要几列 let cWidth = document.documentElement.clientWidth || document.body.clientWidth; // 假设图片宽度为140px let cLen = Math.floor((cWidth/140)-1)
3、假设根据判断需要n列,那么将获取到的数据前n个一次push到每一列,成为第一个元素
// 初始化每一列的第一个元素,this.contentArr为获取到的数据 for (let i = 0; i < cLen; i++) { this.contentArr[i].top = 0 //预设距离顶部值为0 this.columns.push({columnArr:[this.contentArr[i]]}) }
4、此时每一列都有了元素,找到高度最小的那一列,这里可能有多列,找到索引最小那列,然后将元素插入
// 判断应该放到哪一列 for (var index = cLen; index < contentLen; index++) { this.arrIndex = [] let arr = [] let minHeight = 0 let pushIndex = 0 for (let i = 0; i < this.columns.length; i++) { arr.push({ height:this.columns[i].columnArr[this.columns[i].columnArr.length-1].height, top:this.columns[i].columnArr[this.columns[i].columnArr.length-1].top }) } minHeight = this.getMinHeight(arr) //找到高度最小的一列,可能有多个 this.getMinIndex(minHeight) //高度最小的一列所在位置的索引,用pushIndex存储 if(this.arrIndex.length>0){ pushIndex = Math.min.apply(null,this.arrIndex) //出现高度一样的,去索引最小的 } this.contentArr[index].top = minHeight +20 this.columns[pushIndex].columnArr.push(this.contentArr[index]) }
5、当滚动到底部时,请求数据,将获得的数据插入到已有的瀑布流中
document.onscroll = (e) =>{ let top = e.target.documentElement.scrollTop || e.target.body.scrollTop let height = e.target.documentElement.scrollHeight || e.target.body.scrollHeight if((top+clientH)==height){ this.loading = true this.pushElement().then(() =>{ // 从接口最新获取的元素push到目前高度最小的一列 for (var index = 0; index < this.contentArr2.length; index++) { this.arrIndex = [] let arr = [] //找到高度最小的一列,可能有多个 let minHeight = 0 //高度最小的一列的高度 let pushIndex = 0 //高度最小的一列所在位置的索引 for (let i = 0; i < this.columns.length; i++) { arr.push({ height:this.columns[i].columnArr[this.columns[i].columnArr.length-1].height, top:this.columns[i].columnArr[this.columns[i].columnArr.length-1].top }) } minHeight = this.getMinHeight(arr) this.getMinIndex(minHeight) if(this.arrIndex.length>0){ pushIndex = Math.min.apply(null,this.arrIndex) //出现高度一样的,去索引最小的 } this.contentArr[index].top = minHeight +20 this.columns[pushIndex].columnArr.push(this.contentArr[index]) this.loading = false } }) } }
完整代码
<template> <div style="position:relative;"> <div class="container"> <div class="column" v-for="(column,index) in columns" :key="index"> <div class="item" v-for="(item,i) in column.columnArr" :key="i" :style="{ height: item.height + 'px', background: item.background }" > {{ item.text }} </div> </div> </div> <div class="loading" v-if="loading" v-loading="loading" element-loading-text="加载中" element-loading-spinner="el-icon-loading" element-loading-background="rgba(0, 0, 0, 0.8)"></div> </div> </template> <script> export default { data() { return { contentArr: [ { value: 0, height: "150", background: "#409eff", text: "1", top: "" }, { value: 1, height: "250", background: "#67c23a", text: "2", top: "" }, { value: 2, height: "250", background: "#e6a23c", text: "3", top: "" }, { value: 3, height: "250", background: "#f56c6c", text: "4", top: "" }, { value: 4, height: "180", background: "#909399", text: "5", top: "" }, { value: 5, height: "250", background: "#409eff", text: "6", top: "" }, { value: 6, height: "180", background: "#67c23a", text: "7", top: "" }, { value: 7, height: "250", background: "#e6a23c", text: "8", top: "" }, { value: 8, height: "180", background: "#f56c6c", text: "9", top: "" }, { value: 9, height: "150", background: "#909399", text: "10", top: "" }, { value: 10, height: "150", background: "#409eff", text: "11", top: "" }, { value: 11, height: "250", background: "#67c23a", text: "12", top: "" }, { value: 12, height: "250", background: "#e6a23c", text: "13", top: "" }, { value: 13, height: "250", background: "#f56c6c", text: "14", top: "" }, { value: 14, height: "180", background: "#909399", text: "15", top: "" }, { value: 15, height: "250", background: "#409eff", text: "16", top: "" }, { value: 16, height: "180", background: "#67c23a", text: "17", top: "" }, { value: 17, height: "250", background: "#e6a23c", text: "18", top: "" }, { value: 18, height: "180", background: "#f56c6c", text: "19", top: "" }, { value: 19, height: "150", background: "#909399", text: "20", top: "" }, { value: 20, height: "150", background: "#409eff", text: "21", top: "" }, { value: 21, height: "250", background: "#67c23a", text: "22", top: "" }, { value: 22, height: "250", background: "#e6a23c", text: "23", top: "" }, { value: 23, height: "250", background: "#f56c6c", text: "24", top: "" }, { value: 24, height: "180", background: "#909399", text: "25", top: "" }, { value: 25, height: "250", background: "#409eff", text: "26", top: "" }, { value: 26, height: "180", background: "#67c23a", text: "27", top: "" }, { value: 27, height: "250", background: "#e6a23c", text: "28", top: "" }, { value: 28, height: "180", background: "#f56c6c", text: "29", top: "" }, { value: 29, height: "150", background: "#909399", text: "30", top: "" }, ], columns: [], arrIndex:[], loading:false, contentArr2:[] }; }, methods: { pushElement(){ return new Promise((resolve,reject) =>{ setTimeout(() =>{ for (let i = 0; i < 20; i++) { this.contentArr2[i] = { value: i+this.contentArr.length, height: 50*Math.random()+50, background: "#409eff", text: i+this.contentArr.length, top: "" } } resolve() },500) }) }, getMinHeight(arr){ let a = [] for (let i = 0; i < arr.length; i++) { a.push(parseInt(arr[i].height)+parseInt(arr[i].top)) } return Math.min.apply(null,a) }, getMinIndex(val){ for (let i = 0; i < this.columns.length; i++) { let height = this.columns[i].columnArr[this.columns[i].columnArr.length-1].height let top = this.columns[i].columnArr[this.columns[i].columnArr.length-1].top if(parseInt(height)+parseInt(top)==val){ this.arrIndex.push(i) } } }, init() { this.columns = [] let contentLen = this.contentArr.length // 根据可视区域的宽度判断需要几列 let cWidth = document.documentElement.clientWidth || document.body.clientWidth; // 假设图片宽度为100px let cLen = Math.floor((cWidth/140)-1) console.log(cLen); // 初始化每一列的第一行元素 for (let i = 0; i < cLen; i++) { this.contentArr[i].top = 0 //预设距离顶部值为0 this.columns.push({columnArr:[this.contentArr[i]]}) } // 对剩余元素的判断,应该放到哪一列 for (var index = cLen; index < contentLen; index++) { this.arrIndex = [] let arr = [] //找到高度最小的一列,可能有多个 let minHeight = 0 //高度最小的一列的高度 let pushIndex = 0 //高度最小的一列所在位置的索引 for (let i = 0; i < this.columns.length; i++) { arr.push({ height:this.columns[i].columnArr[this.columns[i].columnArr.length-1].height, top:this.columns[i].columnArr[this.columns[i].columnArr.length-1].top }) } minHeight = this.getMinHeight(arr) this.getMinIndex(minHeight) if(this.arrIndex.length>0){ pushIndex = Math.min.apply(null,this.arrIndex) //出现高度一样的,去索引最小的 } this.contentArr[index].top = minHeight +20 this.columns[pushIndex].columnArr.push(this.contentArr[index]) } }, }, mounted() { this.init() window.onresize = () =>{ console.time('aa') this.init() console.timeEnd('aa') } let clientH = document.documentElement.clientHeight || document.body.clientHeight document.onscroll = (e) =>{ let top = e.target.documentElement.scrollTop || e.target.body.scrollTop let height = e.target.documentElement.scrollHeight || e.target.body.scrollHeight if((top+clientH)==height){ this.loading = true this.pushElement().then(() =>{ // 从接口最新获取的元素push到目前高度最小的一列 for (var index = 0; index < this.contentArr2.length; index++) { this.arrIndex = [] let arr = [] //找到高度最小的一列,可能有多个 let minHeight = 0 //高度最小的一列的高度 let pushIndex = 0 //高度最小的一列所在位置的索引 for (let i = 0; i < this.columns.length; i++) { arr.push({ height:this.columns[i].columnArr[this.columns[i].columnArr.length-1].height, top:this.columns[i].columnArr[this.columns[i].columnArr.length-1].top }) } minHeight = this.getMinHeight(arr) this.getMinIndex(minHeight) if(this.arrIndex.length>0){ pushIndex = Math.min.apply(null,this.arrIndex) //出现高度一样的,去索引最小的 } this.contentArr[index].top = minHeight +20 this.columns[pushIndex].columnArr.push(this.contentArr[index]) this.loading = false } }) } } }, }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> div, p { margin: 0; padding: 0; } .container { margin: 0 auto; padding-bottom: 20px; display: flex; justify-content: space-around; background: pink; transition: all 0.5s ease-in-out; } .item { width: 120px; color: #000; margin-top: 20px; display: flex; justify-content: center; align-items: center; transition: all 0.5s ease-in-out; } .loading{ position:fixed; top:0; left:0; right:0; bottom:0; } </style>