什么,你还不会手撕vue3瀑布流组件

瀑布流布局是一种流行的布局方式,常见于图片展示、电商产品列表等场景。

它的核心思想是将元素按照一定规则分配到多列中,每列的高度由其中的元素决定,从而实现了一种类似自然堆砌的视觉效果。

相比传统的网格布局,瀑布流布局更加灵活、动态,能更好地利用空间。

这个瀑布流组件的实现采用了 Vue 3 的 Setup 语法,充分利用了 Composition API 的特性。它定义了一些重要的响应式数据,如 props 用于接收外部传入的列表数据、列宽、列间距等配置项,emits 用于触发自定义事件通知父组件,wrappercontent 分别用于获取容器元素和内容元素的引用。

具体的代码实现

xml 复制代码
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, withDefaults } from 'vue'

const props = withDefaults(
  defineProps<{
    list: any[]
    columnWidth?: number // 列宽
    columnGap?: number // 列间距
    hasMore: boolean
  }>(),
  {
    columnWidth: 172,
    columnGap: 20,
    hasMore: true,
  },
)

const emits = defineEmits(['loadMore'])
const wrapper = ref<HTMLElement | null>(null)
const content = ref<HTMLElement | null>(null)
const flowHeight: number[] = []

/// 绘制瀑布流
function flowDraw() {
  if (!content.value)
    return
  // 初始化列高
  const columnCount = getColumnCount()
  flowHeight.length = columnCount
  for (let i = 0; i < columnCount; i++) {
    flowHeight[i] = 0
  }
  // 设置容器宽(居中布局)
  const itemW = props.columnWidth + props.columnGap
  content.value.style.width = `${itemW * columnCount - props.columnGap}px`

  // 绘制 item 位置
  const doms = content.value.querySelectorAll('.WaterfallItem')
  doms.forEach((dom: any) => {
    const minIdx = getMinIndex(flowHeight)
    dom.style.left = `${minIdx * itemW}px`
    dom.style.top = `${flowHeight[minIdx]}px`
    flowHeight[minIdx] += dom.offsetHeight
  })
  // 设置容器高
  content.value.style.height = `${Math.max(...flowHeight)}px`
}

/// 获取列的数量
function getColumnCount(): number {
  if (!wrapper.value)
    return 0
  const itemW = props.columnWidth + props.columnGap
  const num = (wrapper.value.offsetWidth + props.columnGap) / itemW
  return Math.min(Math.floor(num), props.list.length)
}

/// 获取最小值的索引 index
function getMinIndex(list: number[]) {
  const min = Math.min(...list)
  return list.indexOf(min)
}

/// 监听窗口变化重绘瀑布流布局
const timer = ref()
function onResize() {
  if (timer.value) {
    clearTimeout(timer.value)
    timer.value = null
  }
  timer.value = setTimeout(() => {
    flowDraw()
  }, 300)
}
const loadMoreTrigger = ref()
const observer = ref<IntersectionObserver | null>(null)
function handleIntersect(entries) {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      loadMore()
    }
  })
}
function loadMore() {
  if (!props.hasMore)
    return
  console.log('1111', 1111)
  emits('loadMore')
  onResize()
}
onMounted(() => {
  flowDraw()
  window.addEventListener('resize', onResize)
  observer.value = new IntersectionObserver(handleIntersect)
  observer.value.observe(loadMoreTrigger.value)
})

onBeforeUnmount(() => {
  window.removeEventListener('resize', onResize)
})
</script>

重点分析

组件的核心逻辑位于 flowDraw 函数中,它负责计算并绘制每个元素的位置。首先,它根据容器宽度和配置的列宽、列间距计算出列数,并初始化每列的高度为 0。然后,它遍历所有元素,找到当前高度最小的那一列,并将该元素放置在该列的顶部,同时更新该列的高度。最后,它设置容器的宽度和高度,以适应所有元素。

为了提高性能,组件使用了防抖技术处理窗口大小变化事件。当窗口大小发生变化时,它会延迟 300 毫秒后再重新计算布局,避免频繁的重绘和重排。

另一个重要的功能是上拉加载更多。组件使用了 IntersectionObserver API 来监听一个触发元素是否进入视口。当触发元素进入视口时,就会调用 loadMore 函数,该函数会通知父组件加载更多数据,并在新数据到达后重新计算布局。值得注意的是,组件还维护了一个 hasMore 状态,当没有更多数据时,它会禁止继续加载,并在界面上显示相应的提示,没有数据就显示没有更多了...

布局的样式代码,这里留插槽保证可扩展性

xml 复制代码
<template>
  <div ref="wrapper">
    <div ref="content" class="WaterfallContent">
      <div v-for="(item, index) in list" :key="index" class="WaterfallItem">
        <slot name="item" :index="index" :item="item" />
      </div>
    </div>
    <div ref="loadMoreTrigger" class="flex justify-center">
      {{ hasMore ? '加载更多中...' : '没有更多了...' }}
    </div>
  </div>
</template>

<style lang="scss" scoped>
.WaterfallContent {
  margin-left: auto;
  margin-right: auto;
  position: relative;
}
.WaterfallItem {
  padding-bottom: 24px;
  width: v-bind('`${columnWidth}px`');
  position: absolute;
  top: 0;
  left: 0;
}
</style>

注意:

除了功能实现外,这个组件还注重了可扩展性和可维护性。使用了 Vue 的插槽机制,允许父组件自定义每个元素的内容和样式。

同时,它将样式和逻辑分离,使用 Scoped CSS 确保样式的局部作用域,避免样式污染。

组件销毁时记得把监听器取消哦

总结:

总的来说,这个瀑布流组件通过合理的架构设计、高效的算法实现和良好的性能优化,为开发者提供了一种灵活、强大的解决方案。

它不仅能满足基本的瀑布流布局需求,还能通过配置项和插槽机制进行定制化扩展,适用于各种场景。

无论是个人项目还是企业级应用,都可以借助这个组件快速构建出优雅的瀑布流体验。

相关推荐
哑巴语天雨38 分钟前
React+Vite项目框架
前端·react.js·前端框架
初遇你时动了情1 小时前
react 项目打包二级目 使用BrowserRouter 解决页面刷新404 找不到路由
前端·javascript·react.js
乔峰不是张无忌3301 小时前
【HTML】动态闪烁圣诞树+雪花+音效
前端·javascript·html·圣诞树
鸿蒙自习室1 小时前
鸿蒙UI开发——组件滤镜效果
开发语言·前端·javascript
m0_748250741 小时前
高性能Web网关:OpenResty 基础讲解
前端·openresty
前端没钱2 小时前
从 Vue 迈向 React:平滑过渡与关键注意点全解析
前端·vue.js·react.js
NoneCoder2 小时前
CSS系列(29)-- Scroll Snap详解
前端·css
无言非影2 小时前
vtie项目中使用到了TailwindCSS,如何打包成一个单独的CSS文件(优化、压缩)
前端·css
我曾经是个程序员2 小时前
鸿蒙学习记录
开发语言·前端·javascript
羊小猪~~3 小时前
前端入门之VUE--ajax、vuex、router,最后的前端总结
前端·javascript·css·vue.js·vscode·ajax·html5