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

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

总结:

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

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

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

相关推荐
xiao-xiang9 分钟前
jenkins-通过api获取所有job及最新build信息
前端·servlet·jenkins
C语言魔术师26 分钟前
【小游戏篇】三子棋游戏
前端·算法·游戏
匹马夕阳2 小时前
Vue 3中导航守卫(Navigation Guard)结合Axios实现token认证机制
前端·javascript·vue.js
你熬夜了吗?2 小时前
日历热力图,月度数据可视化图表(日活跃图、格子图)vue组件
前端·vue.js·信息可视化
桂月二二8 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
hunter2062069 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb9 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角9 小时前
CSS 颜色
前端·css
九酒10 小时前
从UI稿到代码优化,看Trae AI 编辑器如何帮助开发者提效
前端·trae
浪浪山小白兔10 小时前
HTML5 新表单属性详解
前端·html·html5