什么,你还不会手撕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 确保样式的局部作用域,避免样式污染。

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

总结:

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

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

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

相关推荐
学不会•1 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
活宝小娜4 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点4 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow4 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o4 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
开心工作室_kaic5 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā5 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
沉默璇年6 小时前
react中useMemo的使用场景
前端·react.js·前端框架
yqcoder6 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
2401_882727576 小时前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架