基于 Vue 3 的瀑布流布局实现(AI依据代码生成 Blog)

背景

瀑布流布局是一种广泛应用于图片、内容展示的方式,尤其在展示动态加载内容时。本文将基于 Vue 3 实现一个简易的瀑布流布局,支持动态加载和随机生成图片。


代码实现

1. 项目结构

组件模板采用 Vue 3<script setup> 形式实现。主要部分包括:

  • 模板结构:定义基本的瀑布流布局框架。
  • 脚本逻辑:计算列数、动态加载内容、监测滚动事件。
  • 样式设计:通过 CSS 实现列和单元的布局。

2. 代码详解

模板部分

<template>
<div id="container" ref="container">
<slot name="empty"></slot>
<div class="container-content">
<div class="column-item" v-for="i in getColumnCount" :key="i"></div>
</div>
<div id="load-more">加载更多...</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from "vue";

const container = ref(); // 瀑布流容器
const pageOptions = reactive({
page: 1, // 当前页码
size: 20, // 每次加载的数据量
});
const results = ref([]); // 存储加载的数据
const props = defineProps({
data: { type: Array, required: true },
columnWidth: { type: Number, default: 200 },
columnSpace: { type: Number, default: 10 },
rowSpace: { type: Number, default: 10 },
});

// 动态计算列数
const getColumnCount = computed(() => {
const w = container.value?.clientWidth;
return w
? Math.floor(
(w + props.columnSpace) / (props.columnWidth + props.columnSpace)
)
: 0;
});

// 随机生成图片数据
function getItems(len) {
const items: any = [];
for (let i = 0; i < len; i++) {
const width = Math.floor(Math.random() * (400 - 300 + 1) + 300);
const height = Math.floor(Math.random() * (500 - 250 + 1) + 250);
let url = `
data:image/svg+xml;charset=utf-8,
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle">
${width} * ${height}
</text>
</svg>`;
items.push(url.trim());
}
return items;
}

// 加载数据
function loadMore() {
setTimeout(() => {
const res = getItems(pageOptions.size);
results.value = results.value.concat(res);

res.forEach((element, index) => {
const newDom = putDom(element, index + pageOptions.size * (pageOptions.page - 1));
requestAnimationFrame(() => {
const short = getTheShortOne();
short?.appendChild(newDom);
});
});
pageOptions.page += 1;
}, 200);
}

// 将图片数据插入 DOM
function putDom(url, index) {
const div = document.createElement("div");
const img = document.createElement("img");
const text = document.createElement("div");
div.appendChild(img);
div.appendChild(text);
div.className = "cell-item";
img.src = url;
text.textContent = index + 1;
return div;
}

// 找到最短列
function getTheShortOne() {
const columnItems = document.querySelectorAll(".column-item");
if (columnItems.length === 0) return;
let minElement = columnItems[0];
let minHeight = minElement.clientHeight;
columnItems.forEach((item) => {
const itemHeight = item.clientHeight;
if (itemHeight < minHeight) {
minHeight = itemHeight;
minElement = item;
}
});
return minElement;
}

// 添加滚动监听
function addObserve() {
const observe = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && results.value.length > 0) {
loadMore();
}
});
});
const dom = document.getElementById("load-more");
if (dom) observe.observe(dom);
}

onMounted(() => {
loadMore();
addObserve();
});
</script>
<style lang="scss">
.container {
overflow-y: auto;
position: relative;
}
.container-content {
--column-gap: 15px;
--column-width: 200px;
--row-space: 15px;

display: flex;
gap: 0 var(--column-gap);
width: 100%;
justify-content: center;

.column-item {
width: var(--column-width);
display: flex;
flex-direction: column;
gap: var(--row-space) 0;
height: fit-content;
}

.cell-item {
font-size: 16px;
font-weight: 500;
display: flex;
flex-direction: column;

img {
width: 100%;
object-fit: cover;
background-color: grey;
}

div {
margin-top: 14px;
line-height: 18px;
}
}
}
#load-more {
text-align: center;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
}
</style>

待优化点

  • dom复杂时的高度计算问题(最好是,计算高度,缓存高度)
  • 大量数据, 内存压力问题(虚拟滚动的支持)

总结

本文通过 Vue 3 和原生 JS 实现了一个功能完整的瀑布流组件,适合图片和内容流的展示场景。实现了动态加载、随机图片生成,以及高效的布局调整。