适用各端瀑布流(可翻页)

163 阅读1分钟

以下以微信小程序为例

<template>
  <view class="box">
    <view
      v-for="(item, index) in list"
      :key="index"
      :class="`card card${index + 1}`"
      :style="'position:absolute;top:' + item.top + 'px;left:' + item.left + 'px'"
      >{{ item }}</view
    >
  </view>
</template>

<script lang="ts">
import { reactive, toRefs } from 'vue'
import Taro, { nextTick, useDidShow, useReachBottom } from '@tarojs/taro'

export default {
  setup() {
    const _data = reactive({
      list: [
        {
          a: '1111',
          b: '2222',
        },
        '2',
        '3',
        '4',
        '5',
        '6',
        '7',
        '8',
        '9',
      ], // 列表数据
      screenWidth: 375,
    })
    // 保存每列高度
    let _columnHeightArr = []
    // 记录卡片位置的数组
    let result = []
    useDidShow(() => {
      nextTick(() => {
        waterFall('.card', { gap: 10 }).then((arr) => {
          if (!arr.length) return
          _data.list = _data.list.map((item, index) => ({ ...item, ...arr[index] }))
        })
      })
    })
    useReachBottom(() => {
      const oldListLen = _data.list.length
      _data.list = _data.list.concat(['10', '11', '12'])
      setTimeout(() => {
        waterFall('.card', { gap: 10, startIndex: oldListLen }).then((arr) => {
          if (!arr.length) return
          _data.list = _data.list.map((item, index) => ({ ...item, ...arr[index] }))
        })
      }, 200)
    })
    const getAllRect = (selector) => {
      return new Promise((resolve) => {
        Taro.createSelectorQuery()
          .selectAll(selector)
          .boundingClientRect()
          .exec((rect) => {
            if (rect === void 0) {
              rect = []
            }
            return resolve(rect[0])
          })
      })
    }
    const waterFall = async (selector, options = {}) => {
      let items = await getAllRect(selector)
      if (items.length <= 0) return []
      // gap: 卡片之间的间距;column:列数;width:屏幕的宽度;firstColumnToTop:第一行距离顶部的高度;startIndex:开始计算位置的数组元素的索引值
      let { startIndex = 0, gap = 15, column = 2, padding = 0, width = 375, firstColumnToTop = 0 } = options
      // 列的宽度
      let itemWidth = items[0].width
      // 根据列宽计算出列与列,行与行之间的间距
      if (gap === 'auto') {
        gap = (width - itemWidth * column) / (column - 1)
      }

      for (let i = startIndex, len = items.length; i < len; i++) {
        if (i < column) {
          // 确定第一行
          let top = firstColumnToTop
          let left = (itemWidth + gap) * i + padding
          // 瀑布流列表左右padding
          if (i === 0 || i === len - 1) {
            left = padding
          }
          _columnHeightArr.push(items[i].height + top)
          result.push({
            top,
            left,
            height: items[i].height,
          })
        } else {
          // 找到数组中最小高度  和 它的索引
          let minHeight = Math.min(..._columnHeightArr)
          let minIndex = _columnHeightArr.findIndex((item) => item === minHeight)
          // 设置下一行的第一个盒子位置
          // top值就是最小列的高度 + gap
          result.push({
            top: _columnHeightArr[minIndex] + gap,
            left: result[minIndex].left,
            height: items[i].height,
          })

          // 修改最小列的高度
          // 最小列的高度 = 当前自己的高度 + 拼接过来的高度 + 间隙的高度
          _columnHeightArr[minIndex] = _columnHeightArr[minIndex] + items[i].height + gap
        }
      }
      return result
    }
    return {
      ...toRefs(_data),
    }
  },
}
</script>

<style lang="scss">
.box {
  display: flex;
  flex-wrap: wrap;
  padding: 0 10px;
}
.card {
  width: 150px;
  height: 150px;
  background-color: rebeccapurple;
  &1 {
    width: 150px;
    height: 250px;
    background-color: gold;
  }
  &2 {
    width: 150px;
    height: 240px;
    background-color: darkblue;
  }
  &3 {
    width: 150px;
    height: 230px;
    background-color: #4fc08d;
  }
  &4 {
    width: 150px;
    height: 220px;
    background-color: #7d8d8a;
  }
  &5 {
    width: 150px;
    height: 195px;
    background-color: chocolate;
  }
  &6 {
    width: 150px;
    height: 170px;
    background-color: aqua;
  }
  &7 {
    width: 150px;
    height: 250px;
    background-color: #1f2136;
  }
  &8 {
    width: 150px;
    height: 190px;
    background-color: wheat;
  }
  &9 {
    width: 150px;
    height: 210px;
    background-color: #00a4aa;
  }
}
</style>