backgroundbackground

Vue瀑布流式布局组件

WaterfallLayout / Vue / TailwindCSS

技术

2025-03-23 06:06

简介

使用VueTailwindCSS封装的前端瀑布流式布局组件,可指定列数,自动计算平衡列

使用方法

<WaterfallLayout :columnCount="3" :items="items">
    <template #item="{ item }">
        {{ item }}
    </template>
</WaterfallLayout>

代码

文件名:WaterfallLayout.vue

<template>
  <div class="relative flex w-full" :style="{ gap: horizontalGap }" ref="containerRef">
    <div
      v-for="(column, columnIndex) in columns"
      :key="columnIndex"
      class="flex flex-col"
      :style="{
        width: `${100 / props.columnCount}%`,
        gap: verticalGap
      }"
    >
      <div v-for="item in column" :key="item.id" class="w-full" ref="itemRefs">
        <slot name="item" :item="item.data" />
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, watch, computed } from 'vue'

interface WaterfallItem<T> {
  id: number
  height: number
  data: T
}

const props = withDefaults(
  defineProps<{
    columnCount: number
    items: any[] // 传入的数据列表
    horizontalGap?: string
    verticalGap?: string
  }>(),
  {
    horizontalGap: '1rem',
    verticalGap: '1rem'
  }
)

const horizontalGap = computed(() => props.horizontalGap)
const verticalGap = computed(() => props.verticalGap)

const containerRef = ref<HTMLElement | null>(null)
const itemRefs = ref<HTMLElement[]>([])
const columns = ref<WaterfallItem<any>[][]>([])

// 初始化列数组
const initializeColumns = () => {
  columns.value = Array(props.columnCount)
    .fill(null)
    .map(() => [])
}

// 找到最短的列
const findShortestColumn = () => {
  const columnHeights = columns.value.map((column) => {
    return column.reduce((height, item) => height + item.height, 0)
  })
  return columnHeights.indexOf(Math.min(...columnHeights))
}

// 处理项目分布
const arrangeItems = () => {
  initializeColumns()

  props.items.forEach((data, index) => {
    const height = itemRefs.value[index]?.offsetHeight ?? 0
    const item: WaterfallItem<any> = {
      id: index,
      height,
      data
    }

    const shortestColumnIndex = findShortestColumn()
    columns.value[shortestColumnIndex].push(item)
  })
}

// 监听数据变化
watch(() => props.items, arrangeItems, { deep: true })
watch(() => props.columnCount, arrangeItems)

// 监听容器大小变化
const resizeObserver = new ResizeObserver(() => {
  arrangeItems()
})

onMounted(() => {
  if (containerRef.value) {
    resizeObserver.observe(containerRef.value)
  }
  arrangeItems()
})
</script>