基本情况
项目基础条件介绍:移动端,vue3
实现一个类似小红书的瀑布流布局
分析需求
小红书的瀑布流总体呈现以下特点
- 封面尺寸大体一致(有时候封面尺寸也会不一样,这个暂时先忽略)
- 标题所占行数不同
所以标题 所占行数不同,是实现瀑布流效果的关键因素
代码相关
- 直接定义两列(left, right)数组,并编写 html 模板
const leftList = ref([])
const rightList = ref([])
<view >
<PostCard v-for="item in leftList" :key="item.id" />
</view>
<view >
<PostCard v-for="item in rightList" :key="item.id" />
</view>
- 关键来了,定义 computed ,用来得知哪一个列最后的元素更长,以此来判断新元素要放到哪一个列上
const leftLastTitleLength = computed(() => {
const lastEl = leftList.value[leftList.value.length - 1]
return lastEl ? lastEl.title.length : 0
})
const rightLastTitleLength = computed(() => {
const lastEl = rightList.value[rightList.value.length - 1]
return lastEl ? lastEl.title.length : 0
})
还记得我们之前说的,标题行数不同是实现瀑布流的关键因素吗?所以在这里,你就看到了 lastEl.title 作为判断的核心代码
- 定义添加元素函数
const pushDo = (value) => {
value.forEach((item) => {
if (leftLastTitleLength.value === rightLastTitleLength.value) {
leftList.value.push(item)
return
}
if (leftLastTitleLength.value > rightLastTitleLength.value) {
rightList.value.push(item)
return
}
if (leftLastTitleLength.value < rightLastTitleLength.value) {
leftList.value.push(item)
return
}
})
}
value 是数组。在这个函数中,有 3 种情况的添加方式,左右相同时,左大于右时,右大于左时。
- 网络请求数据,逐一将元素添加至左右两列中,瀑布流就实现了
const getData().then((res) => {
pushDo(res.data)
})
写在最后
为了方便大家使用这套方案,我特地封装了 ts 版的 hooks 函数。如果恰巧你也在用 vue3 ,可以直接复制拿去使用
如下使用
const waterFallHook = WaterFallHook<PosterType.PolicyRecordWithUser>('title')
waterFallHook.pushDo(list)
Hook 函数
import { computed, Ref, ref } from 'vue'
export default function useWaterfall<T>(key: string): {
emptyData: Ref<boolean>
leftList: Ref<T[]>
rightList: Ref<T[]>
pushDo: (value: T[]) => void
clear: () => void
} {
const leftList = ref([]) as Ref<T[]>
const rightList = ref([]) as Ref<T[]>
const emptyData = computed(() => {
return leftList.value.length === 0 && rightList.value.length === 0
})
const leftLastTitleLength = computed(() => {
const lastEl = leftList.value[leftList.value.length - 1]
return lastEl ? lastEl[key].length : 0
})
const rightLastTitleLength = computed(() => {
const lastEl = rightList.value[rightList.value.length - 1]
return lastEl ? lastEl[key].length : 0
})
const pushDo = (value) => {
value.forEach((item) => {
if (leftLastTitleLength.value === rightLastTitleLength.value) {
leftList.value.push(item)
return
}
if (leftLastTitleLength.value > rightLastTitleLength.value) {
rightList.value.push(item)
return
}
if (leftLastTitleLength.value < rightLastTitleLength.value) {
leftList.value.push(item)
return
}
})
}
const clear = () => {
leftList.value = []
rightList.value = []
}
return {
emptyData,
leftList,
rightList,
pushDo,
clear,
}
}