vue 极简方案瀑布流实现,易理解,很实用,好掌握

215 阅读2分钟

基本情况

项目基础条件介绍:移动端,vue3

实现一个类似小红书的瀑布流布局

分析需求

小红书的瀑布流总体呈现以下特点

  1. 封面尺寸大体一致(有时候封面尺寸也会不一样,这个暂时先忽略)
  2. 标题所占行数不同

所以标题 所占行数不同,是实现瀑布流效果的关键因素

IMG_20240807_100250.jpg

代码相关

  1. 直接定义两列(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>
  1. 关键来了,定义 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 作为判断的核心代码

  1. 定义添加元素函数
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 种情况的添加方式,左右相同时,左大于右时,右大于左时。

  1. 网络请求数据,逐一将元素添加至左右两列中,瀑布流就实现了
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,
  }
}