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格式