v3-瀑布流组件封装

post on 2025-03-11 10:41:41
前端
vue3
瀑布流
0107

记录一下封装的瀑布流组件

<template>
  <div class="relative" ref="containerRef" :style="{ height: `${contentHeight}px` }">
    <div
      class="item absolute top-0 left-0 transform"
      v-for="item, index in props.list"
      :key="index"
      :style="{ width: `${columnWidth}px` }"
    >
      <slot name="item" :item="item" />
    </div>
  </div>
</template>

<script setup lang="ts" generic="T">
import { useElementSize, useThrottleFn } from '@vueuse/core'
import { computed, onMounted, watch, nextTick, ref, reactive } from 'vue'

const props = withDefaults(defineProps<{
  list?: T[],
  columns?: number,
  gapX?: number,
  gapY?: number,
}>(), {
  list: () => [],
  columns: 2,
  gapX: 10,
  gapY: 10,
})

const containerRef = ref<HTMLDivElement>()
const { width: containerWidth } = useElementSize(containerRef)

// 列高度数组
const columnHeights = reactive<number[]>([])

// 列宽计算
const columnWidth = computed(() => {
  if (!containerWidth.value || props.columns === 0) return 0
  return (containerWidth.value - (props.columns - 1) * props.gapX) / props.columns
})

// 内容高度
const contentHeight = computed(() => Math.max(...columnHeights))

// 瀑布流布局函数
const layoutItems = useThrottleFn(() => {
  if (!containerRef.value) return
  
  // 1. 重置列高度数组
  columnHeights.splice(0, columnHeights.length, ...new Array(props.columns).fill(0))

  // 2. 获取所有子元素
  const items = Array.from(containerRef.value.children) as HTMLElement[]
  
  // 3. 遍历计算每个元素位置
  items.forEach(itemEl => {
    // 获取元素实际高度
    const height = itemEl.offsetHeight
    
    // 找到当前最短列
    const minHeight = Math.min(...columnHeights)
    const columnIndex = columnHeights.indexOf(minHeight)
    
    // 计算布局位置
    const left = columnIndex * (columnWidth.value + props.gapX)
    const top = columnHeights[columnIndex]
    
    // 应用样式
    itemEl.style.transform = `translate(${left}px, ${top}px)`
    
    // 更新列高度(加上当前元素高度和间隙)
    columnHeights[columnIndex] += height + props.gapY
  })

  // 4. 修正最后一行的多余间隙
  columnHeights.forEach((_, i) => columnHeights[i] -= props.gapY)
}, 16)

// 监听相关参数变化
watch(() => [...props.list], () => nextTick(layoutItems), { deep: true })
watch(() => props.columns, layoutItems)
watch(() => [props.gapX, props.gapY], layoutItems, { deep: true })
watch(containerWidth, layoutItems)

// 初始布局
onMounted(() => nextTick(layoutItems))

defineExpose({
  render: layoutItems
})
</script>

<style scoped>
.item {
  will-change: transform;
  backface-visibility: hidden;
}
</style>
说点什么...
评论支持MarkDown格式

 / 1