初衷以及大致的思路
前几个月公司需要整一个瀑布流的列表,看了下社区里面的瀑布流列表,感觉多多少少好像有点问题,于是就自己动手整了个,目前适配了ios和安卓、H5,微信小程序还没测。。。所以不知道咋样。。。目前还是挺好用的,H5直接用ref可以省下不少事件,但是app端炸了,所以放弃了ref,等待DOM生成后再来插入下一条数据
代码实现
通过flex,实现最基本两列布局,然后通过追加数组的方式,一条条插入左右两列,分别为list0和list1
最开始的两条数据,我们可以直接分配位置
先计算所有这条数据需要异步加载资源的图片高度(正常只有一张图片),然后获取到这些高度后,直接给定高度,防止图片撑开后导致回流。
每次追加数据,获取当前list0和list1的高度,比较两边高度,插入最短的列表即可...
大致代码如下:
<template>
<view>
<view class="skeleton" v-if="initLoading">
<view class="skeleton-item" v-for="item in 6" :key="item">
<u-skeleton loading titleWidth="100%" :titleHeight="200"></u-skeleton>
<u-skeleton class="mt8" loading titleWidth="100%" :titleHeight="20"></u-skeleton>
<u-skeleton class="mt8" loading titleWidth="100%" :titleHeight="20"></u-skeleton>
<u-skeleton class="mt8" titleWidth="100%" loading avatarSize="20" avatar></u-skeleton>
</view>
</view>
<view class="case-list">
<view v-for="(item, index) in list" :key="index" class="case-list-container">
<view :id="`list${index}`">
<view
v-for="(childrenItem, childrenIndex) in item"
:key="childrenIndex"
class="case-list-item"
>
// 你的内容组件
</view>
</view>
</view>
</view>
<u-loading-icon v-show="loading" mode="circle" text="正在获取数据中..."></u-loading-icon>
</view>
</template>
<script>
import { getBlendRecommend } from '@/apis/article'
export default {
data() {
return {
list: [[], []],
loading: false,
params: {},
}
},
computed: {
initLoading() {
if (this.loading && !this.list[0].length && !this.list[1].length) {
return true
}
return false
},
windowWidth() {
// 24 - 左右边距总和
const { windowWidth } = uni.getSystemInfoSync()
return Number.parseInt(windowWidth / 2) - 24
},
},
methods: {
// 骨架屏,可以忽略
setLoading(status) {
switch (status) {
case 'waiting':
this.loading = true
break
case 'end':
this.loading = false
break
}
},
// 获取数据
getList() {
if (this.loading) {
return false
}
this.setLoading('waiting')
getBlendRecommend(this.params)
.then(data => {
this.params.page++
if (Array.isArray(data) && data.length) {
let len = 0
// 获取图片的高度
data.forEach(item => {
uni.getImageInfo({
src: item.imgUrl,
success: e => {
item._height = Math.round(this.windowWidth * (e.height / e.width))
const data = JSON.parse(JSON.stringify(item))
// 加入队列
this.handleRearrangement(data)
},
complete: () => {
len++
if (len === data.length) {
this.setLoading('end')
}
},
fail: () => {
len++
},
})
})
} else {
this.setLoading('end')
}
})
.catch(() => {
this.setLoading('end')
})
},
// 计算当前一列的长度 0左 1右
getListHeight() {
const leftList = new Promise(resolve => {
this.$nextTick(() => {
const query = uni.createSelectorQuery().in(this)
query
.select('#list0')
.boundingClientRect(data => {
resolve(data.height)
})
.exec()
})
})
const rightList = new Promise(resolve => {
this.$nextTick(() => {
const query = uni.createSelectorQuery().in(this)
query
.select('#list1')
.boundingClientRect(data => {
resolve(data.height)
})
.exec()
})
})
return Promise.all([leftList, rightList]).then(data => {
return data
})
},
handleRearrangement(row) {
this.getListHeight().then(res => {
const i = res[0] > res[1] ? 1 : 0
row.currentHeight = res
const index = this.list[i].length - 1 >= 0 ? this.list[i].length - 1 : 0
// 如果高度无变化,说明DOM并未生成,给到下次继续判断,不追加数据
if (index && this.list[i][index].currentHeight[i] === res[i]) {
setTimeout(() => {
this.handleRearrangement(row)
}, 100)
} else {
// list0和list1的第一条数据,直接放入
this.list[i].push(row)
}
})
},
handleRefresh() {
this.getList()
},
},
created() {
this.getList()
},
}
</script>