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中的最大值
Vue3+TypeScript实现瀑布流布局_第1张图片
代码实现:

<template>
  <h1 class="h-[50px] text-3xl text-center my-10">Vue3 Waterfall Flow Layouth1>
  <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>

你可能感兴趣的:(Vue.js,前端,typescript,前端,vue,javascript)