瀑布流布局

瀑布流布局

实现思路

首先我们先介绍一下瀑布流布局实现的思路

  1. 控制容器内每一列卡片的宽度相同(不同图片尺寸等比例缩放)
  2. 第一行卡片紧挨着排列,第二行开始,每张卡片摆放到当前所有列中高度最小的一列下面

组件结构

首先展示一下整个DOM的结构

<template>
  <div class="waterfall-container" ref="containerRef" @scroll="handleScroll">
    <div class="waterfall-list" ref="listRef">
      <div
        class="waterfall-item"
        v-for="(item, index) in state.cardList"
        :key="item.id"
        :style="{
          width: `${state.cardWidth}px`,
          transform: `translate3d(${state.cardPos[index].x}px, ${state.cardPos[index].y}px, 0)`,
        }"
      >
        <slot
          name="item"
          :item="item"
          :index="index"
          :imageHeight="state.cardPos[index].imageHeight"
        ></slot>
      </div>
    </div>
  </div>
</template>

主要的结构就是waterfall-container,waterfall-list,waterfall-item

container 作为整个瀑布流的容器,在它上面展示滚动条, list 作为 item 的容器可以开启相对定位,而 item 开启绝对定位,通过 translate 来控制每张卡片的位置实现最后的布局,所以每张卡片定位统一放到左上角即可

<style scoped>
.waterfall-container {
  width: 100%;
  height: 100vh;
  overflow-y: scroll;
  overflow-x: hidden;
}
.waterfall-list {
  width: 100%;
  position: relative;
}
.waterfall-item {
  position: absolute;
  left: 0;
  top: 0;
  box-sizing: border-box;
  transition: all 0.2s;
}
</style>

对于这里用到的数据如下:

const props = defineProps({
//用于拿到数据
  request: {
    type: Function,
    required: true // 确保父组件必须提供一个函数
  },
  //列信息
  column: {
    type: Number,
    default: 4
  },
  //间距
  gap: {
    type: Number,
    default: 10
  },
  //页码,通过这个来获取信息
  pageSize: {
    type: Number,
    default: 20
  },
  //触底加载新数据
  bottom: {
    type: Number,
    default: 20
  }
})
const state = ref({
  isFinish: false,
  loading: false,
  page: 1, //数据的页码
  cardWidth: 0, //卡片的宽度
  cardList: [], //数据源
  cardPos: [], //卡片的位置信息
  columnHeight: new Array(props.column).fill(0), //列高度信息
  preLen: 0
})

函数封装

接下来就是进行计算每张卡片的具体位置,为最后的布局做准备

//计算出高度最小的那一列的索引和高度
const minColumn = computed(() => {
  let minIndex = -1,
    minHeight = Infinity
  state.value.columnHeight.forEach((item, index) => {
    if (item < minHeight) {
      minHeight = item
      minIndex = index
    }
  })
  return {
    minIndex,
    minHeight
  }
})

这里通过遍历存储每列高度信息的数组,返回高度最小的那一列的索引和高度

//根据页码得到数据
const getCardList = async (page, pageSize) => {
  if (state.value.isFinish) {
    return
  }
  state.value.loading = true
  const list = await props.request(page, pageSize)
  state.value.page++
  if (!list.length) {
    state.value.isFinish = true
    return
  }
  state.value.cardList = [...state.value.cardList, ...list]
  computedCardPos(list)
  state.value.loading = false
}

封装获取数据的函数,通过页码来获得新数据

//计算卡片的图片的高度
const computedImageHeight = list => {
  list.forEach(item => {
    const imageHeight = Math.floor(
      (item.height * state.value.cardWidth) / item.width
    )
    state.value.cardPos.push({
      width: state.value.cardWidth,
      imageHeight: imageHeight,
      cardHeight: 0,
      x: 0,
      y: 0
    })
  })
}

这里通过给定的图片宽高信息,和每一列的宽度,进而计算出每张图片缩放之后的高度

//根据真实dom计算出卡片的具体位置
const computedRealDomPos = list => {
  const children = listRef.value.children
  list.forEach((item, index) => {
    const nextIndex = state.value.preLen + index
    const cardHeight = children[nextIndex].getBoundingClientRect().height
    if (index < props.column && state.value.cardList.length <= props.pageSize) {
      state.value.cardPos[nextIndex] = {
        ...state.value.cardPos[nextIndex],
        cardHeight: cardHeight,
        x:
          nextIndex % props.column !== 0
            ? nextIndex * (state.value.cardWidth + props.gap)
            : 0,
        y: 0
      }
      state.value.columnHeight[nextIndex] = cardHeight + props.gap
    } else {
      const { minIndex, minHeight } = minColumn.value
      state.value.cardPos[nextIndex] = {
        ...state.value.cardPos[nextIndex],
        cardHeight: cardHeight,
        x: minIndex ? minIndex * (state.value.cardWidth + props.gap) : 0,
        y: minHeight
      }
      state.value.columnHeight[minIndex] += cardHeight + props.gap
    }
  })
  state.value.preLen = state.value.cardPos.length
}

这个函数则是要计算每张卡片的具体偏移量,做法是对于传入的数据数组,利用记录列高度数据的数组,计算每张卡片的水平和垂直偏移量,再存入数组中

//计算每个卡片的位置
const computedCardPos = async list => {
  computedImageHeight(list)
  await nextTick()
  computedRealDomPos(list)
}

计算每个卡片的位置

//初始化,根据列数计算每列卡片的宽度
const init = () => {
  if (containerRef.value) {
    const containerWidth = containerRef.value.clientWidth
    state.value.cardWidth =
      (containerWidth - props.gap * (props.column - 1)) / props.column
    getCardList(state.value.page, props.pageSize)
    resizeObserver.observe(containerRef.value)
  }
}

onMounted(() => {
  init()
})

初始化函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值