vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > Vue3实现瀑布流

Vue3实现瀑布流的2种核心方案

作者:北极糊的狐

这篇文章主要介绍了Vue3中实现瀑布流的两种主要方案:CSS原生方案和JS计算方案,CSS方案适用于静态短列表,而JS方案适用于长列表和复杂布局,能够保证排序正常并支持懒加载,文章详细介绍了两种方案的实现步骤,需要的朋友可以参考下

查资料Vue3 中实现瀑布流主要有 CSS 原生方案(简单场景) 和 JS 计算方案(复杂场景),整理了两种方法的实现步骤。

一、方案对比

方案核心原理优点缺点适用场景
CSS 方案column-count/grid 布局代码简单、无 JS 计算排序乱(按列排布)、不支持懒加载静态短列表、简单瀑布流
JS 方案计算列高 + 动态插入排序正常、支持懒加载需 JS 计算、适配窗口变化动态加载、长列表、复杂布局

二、方案 1:CSS 原生实现(快速上手)

1. column 多列布局(最简单)

利用 column-count 拆分列,column-gap 控制列间距,适合静态数据展示。

<template>
  <div class="waterfall-css">
    <div 
      class="waterfall-item" 
      v-for="(item, index) in list" 
      :key="index"
    >
      <img :src="item.img" alt="瀑布流图片" class="item-img" />
      <p class="item-desc">{{ item.desc }}</p>
    </div>
  </div>
</template>
 
<script setup>
import { ref } from 'vue';
 
// 模拟瀑布流数据
const list = ref([
  { img: 'https://picsum.photos/300/400?1', desc: '图片1' },
  { img: 'https://picsum.photos/300/300?2', desc: '图片2' },
  { img: 'https://picsum.photos/300/500?3', desc: '图片3' },
  { img: 'https://picsum.photos/300/200?4', desc: '图片4' },
  { img: 'https://picsum.photos/300/600?5', desc: '图片5' },
  { img: 'https://picsum.photos/300/350?6', desc: '图片6' },
]);
</script>
 
<style scoped>
/* 瀑布流容器 */
.waterfall-css {
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  /* 核心:拆分为3列 */
  column-count: 3;
  /* 列间距 */
  column-gap: 16px;
  padding: 16px;
}
 
/* 瀑布流项(避免跨列断裂) */
.waterfall-item {
  /* 关键:禁止跨列 */
  break-inside: avoid;
  margin-bottom: 16px;
  border-radius: 8px;
  background: #fff;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  overflow: hidden;
}
 
/* 图片自适应 */
.item-img {
  width: 100%;
  height: auto;
  display: block;
}
 
.item-desc {
  padding: 12px;
  font-size: 14px;
  color: #333;
}
 
/* 响应式适配 */
@media (max-width: 768px) {
  .waterfall-css {
    column-count: 2; /* 移动端2列 */
  }
}
@media (max-width: 480px) {
  .waterfall-css {
    column-count: 1; /* 小屏1列 */
  }
}
</style>

2. CSS Grid 布局(更灵活)

适合需要自定义列宽、对齐方式的场景,兼容现代浏览器。

<template>
  <div class="waterfall-grid">
    <div 
      class="waterfall-item" 
      v-for="(item, index) in list" 
      :key="index"
    >
      <img :src="item.img" alt="" class="item-img" />
    </div>
  </div>
</template>
 
<script setup>
import { ref } from 'vue';
const list = ref([/* 同上文数据 */]);
</script>
 
<style scoped>
.waterfall-grid {
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  padding: 16px;
  /* Grid 核心配置 */
  display: grid;
  /* 3列,列宽自适应,间距16px */
  grid-template-columns: repeat(3, 1fr);
  grid-gap: 16px;
  /* 行高自适应(关键) */
  grid-auto-rows: minmax(100px, auto);
}
 
.waterfall-item {
  border-radius: 8px;
  overflow: hidden;
}
 
.item-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
 
/* 响应式 */
@media (max-width: 768px) {
  .waterfall-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
</style>

三、方案 2:JS 计算实现(推荐,排序正常)

CSS 方案的缺陷是「元素按列从上到下排布」(而非从左到右),JS 方案通过计算每列高度,将新元素插入最短列,保证排序和布局更符合预期。

完整实现(含懒加载 + 窗口适配)

<template>
  <div class="waterfall-js" ref="waterfallRef">
    <!-- 列容器(动态生成) -->
    <div 
      class="waterfall-column" 
      v-for="(col, index) in columnList" 
      :key="index"
      ref="columnRefs"
    >
      <!-- 列内元素 -->
      <div 
        class="waterfall-item" 
        v-for="item in col" 
        :key="item.id"
        @load="handleImgLoad(item.id)"
      >
        <img 
          :src="item.img" 
          alt="" 
          class="item-img"
          :data-id="item.id"
          loading="lazy" <!-- 原生懒加载 -->
        />
        <p class="item-desc">{{ item.desc }}</p>
      </div>
    </div>
    <!-- 加载更多 -->
    <div class="load-more" v-if="hasMore" @click="loadMore">加载更多</div>
    <div class="no-more" v-else>没有更多数据了</div>
  </div>
</template>
 
<script setup>
import { ref, onMounted, onResize, nextTick } from 'vue';
 
// 核心变量
const waterfallRef = ref(null); // 瀑布流容器
const columnRefs = ref([]); // 列容器引用
const columnCount = ref(3); // 列数(默认3列)
const columnHeights = ref([]); // 每列的高度
const columnList = ref([]); // 每列的元素列表
const hasMore = ref(true); // 是否有更多数据
let page = 1; // 当前页码
 
// 初始化列
const initColumns = () => {
  // 根据屏幕宽度设置列数
  const width = document.documentElement.clientWidth;
  if (width <= 480) {
    columnCount.value = 1;
  } else if (width <= 768) {
    columnCount.value = 2;
  } else {
    columnCount.value = 3;
  }
  // 重置列数据
  columnList.value = Array.from({ length: columnCount.value }, () => []);
  columnHeights.value = Array(columnCount.value).fill(0);
};
 
// 模拟请求数据
const fetchData = async (pageNum) => {
  // 模拟接口延迟
  await new Promise(resolve => setTimeout(resolve, 500));
  const mockData = Array.from({ length: 9 }, (_, i) => ({
    id: `${pageNum}-${i}`,
    img: `https://picsum.photos/300/${200 + Math.random() * 400 | 0}?${pageNum}-${i}`,
    desc: `第${pageNum}页-${i+1}条`
  }));
  return mockData;
};
 
// 分配元素到最短列
const assignToShortestColumn = (data) => {
  data.forEach(item => {
    // 找到最短列的索引
    const minHeightIndex = columnHeights.value.indexOf(Math.min(...columnHeights.value));
    // 插入元素
    columnList.value[minHeightIndex].push(item);
    // 预估值(图片加载前先占位,避免高度计算偏差)
    columnHeights.value[minHeightIndex] += 300; // 临时占位高度
  });
};
 
// 图片加载完成后更新列高度
const handleImgLoad = (id) => {
  nextTick(() => {
    // 找到当前图片元素
    const img = document.querySelector(`img[data-id="${id}"]`);
    if (!img) return;
    const item = img.closest('.waterfall-item');
    const column = item.closest('.waterfall-column');
    // 找到列索引
    const colIndex = columnRefs.value.findIndex(col => col === column);
    if (colIndex === -1) return;
    // 重新计算列高度
    columnHeights.value[colIndex] = column.offsetHeight;
  });
};
 
// 加载更多数据
const loadMore = async () => {
  const data = await fetchData(page);
  if (data.length === 0) {
    hasMore.value = false;
    return;
  }
  assignToShortestColumn(data);
  page++;
};
 
// 窗口大小变化时重新布局
const handleResize = () => {
  initColumns();
  // 重新分配所有数据(合并所有列数据)
  const allData = columnList.value.flat();
  columnList.value = Array.from({ length: columnCount.value }, () => []);
  columnHeights.value = Array(columnCount.value).fill(0);
  assignToShortestColumn(allData);
};
 
// 初始化
onMounted(() => {
  initColumns();
  loadMore();
  // 监听窗口变化(Vue3 内置 onResize)
  onResize(handleResize);
});
</script>
 
<style scoped>
.waterfall-js {
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  padding: 16px;
  /* 弹性布局实现列并排 */
  display: flex;
  gap: 16px; /* 列间距 */
}
 
.waterfall-column {
  /* 列宽均分 */
  flex: 1;
}
 
.waterfall-item {
  margin-bottom: 16px;
  border-radius: 8px;
  background: #fff;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  overflow: hidden;
}
 
.item-img {
  width: 100%;
  height: auto;
  display: block;
}
 
.item-desc {
  padding: 12px;
  font-size: 14px;
  color: #333;
}
 
/* 加载更多样式 */
.load-more, .no-more {
  width: 100%;
  text-align: center;
  padding: 16px;
  font-size: 14px;
  color: #666;
  cursor: pointer;
}
 
.load-more:hover {
  color: #409eff;
}
 
.no-more {
  cursor: default;
  color: #999;
}
</style>

四、核心优化技巧

1. 图片加载优化

2. 窗口适配

3. 性能优化

4. 兼容处理

五、第三方库推荐(懒人方案)

若不想手动实现,可使用成熟的 Vue3 瀑布流组件:

vue3-waterfall-easy:轻量、支持懒加载、响应式,文档友好;

npm install vue3-waterfall-easy --save 

vue-grid-layout:适用于可拖拽的瀑布流布局,支持自定义网格大小;

masonry-layout:经典的瀑布流库,可结合 Vue3 封装使用。

六、总结

  1. 快速实现:用 CSS column-count 或 grid,适合静态短列表;
  2. 生产环境:优先用 JS 计算方案,保证排序正常、支持动态加载;
  3. 核心逻辑:JS 方案的关键是「计算列高 → 插入最短列 → 图片加载后更新高度」;
  4. 优化重点:图片懒加载、窗口适配、减少重排操作。

可根据项目需求选择方案:简单场景用 CSS,复杂场景(动态加载、排序要求)用 JS 方案,或直接使用第三方库提升开发效率。

以上就是Vue3实现瀑布流的2种核心方案的详细内容,更多关于Vue3实现瀑布流的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文