Vue3+TypeScript实现瀑布流布局

瀑布流布局(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>
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值