瀑布流布局(Waterfall Flow Layout)是一种常用于网页设计的布局方式,它可以实现图片或内容的自适应排列,使页面看起来更加美观和流畅。
先看实现效果:
下面是使用 vue 实现瀑布流布局的一般步骤:
1.定义列数、行间距、列间距
2.容器设置为相对定位、item设置为绝对定位
3.获取容器宽度:容器总宽度-内边距(paddingLeft+paddingRight)
4.计算每列宽度:(容器宽度 - 列间距 * (列数 - 1)) / 列数
5.获取每一个item的高度(需要放在nextTick里执行)
6.根据列数生成一个记录item高度的数组columnHeights,数组长度和列数相同,默认填充为 0
7.遍历所有item:计算每个item的left、top
8.container高度:columnHeights中的最大值
代码实现:
<template>
<h1 class="h-[50px] text-3xl text-center my-10">Vue3 Waterfall Flow Layout</h1>
<div class="text-center mb-10">
列数
<input
v-model.number="column"
type="number"
class="outline-none border border-zinc-800 rounded px-2 py-0.5"
/>
</div>
<div
ref="containerRef"
class="px-10 md:px-20 lg:px-40 xl:px-60 relative"
:style="{ height: containerHeight + 'px' }"
>
<div
v-for="item in data"
:key="item.url"
class="item absolute duration-300"
:style="{ width: columnWidth + 'px' }"
>
<img
:src="item.url"
alt=""
:style="{ height: (columnWidth / item.width) * item.height + 'px' }"
class="rounded"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick, watch } from 'vue'
// data 是一个数组,存放图片 url 和宽高,格式为:{ url: 'https://xxxx', width: xx, height: xx }
import data from './data'
const column = ref(4)
const rowSpacing = ref(20)
const columnSpacing = ref(20)
const containerRef = ref<HTMLElement | null>(null)
// 容器总宽度
const containerWidth = ref(0)
// 容器总高度
const containerHeight = ref(0)
// 列宽 = (容器宽度 - 列间距 * (列数 - 1)) / 列数
const columnWidth = ref(0)
const containerLeft = ref(0)
// 计算容器宽度
const useContainerWidth = () => {
const { paddingLeft, paddingRight } = window.getComputedStyle(containerRef.value!)
containerLeft.value = parseFloat(paddingLeft)
containerWidth.value =
containerRef.value!.clientWidth - parseFloat(paddingLeft) - parseFloat(paddingRight)
}
// 计算列宽
const useColumnWidth = () => {
columnWidth.value =
(containerWidth.value - columnSpacing.value * (column.value - 1)) / column.value
}
// 获取最小高度
const getMinHeight = (arr: number[]) => {
return Math.min(...arr)
}
// 获取最小高度的索引
const getMinHeightIndex = (arr: number[]) => {
return arr.indexOf(getMinHeight(arr))
}
// 获取所有图片的高度
const itemHeights = ref<number[]>([])
const useItemHeight = () => {
const allItems = document.querySelectorAll<HTMLElement>('.item')
itemHeights.value = Array.from(allItems).map((item) => item.clientHeight)
}
const columnHeights = ref(Array(column.value).fill(0))
const getItemLeft = () => {
const column = getMinHeightIndex(columnHeights.value)
return (columnWidth.value + columnSpacing.value) * column + containerLeft.value
}
const getItemTop = () => {
return getMinHeight(columnHeights.value)
}
const increaseColumnHeight = (index: number) => {
const minHeightColumnIndex = getMinHeightIndex(columnHeights.value)
columnHeights.value[minHeightColumnIndex] += itemHeights.value[index] + rowSpacing.value
}
// 计算每个 item 的位置
const useItemPosition = () => {
const allItems = document.querySelectorAll<HTMLElement>('.item')
allItems.forEach((item, index) => {
item.style.left = getItemLeft() + 'px'
item.style.top = getItemTop() + 'px'
increaseColumnHeight(index)
})
containerHeight.value = Math.max(...columnHeights.value)
}
onMounted(() => {
useContainerWidth()
useColumnWidth()
nextTick(() => {
useItemHeight()
useItemPosition()
})
})
watch(column, (value) => {
columnHeights.value = Array(value).fill(0)
useColumnWidth()
nextTick(() => {
useItemHeight()
useItemPosition()
})
})
</script>