vue3实现瀑布流布局组件

先看效果图
在这里插入图片描述

直接上代码
utils.js

// 用于模拟接口请求
export const getRemoteData = (data = '获取数据', time = 2000) => {return new Promise((resolve) => {setTimeout(() => {console.log(`模拟获取接口数据`, data)resolve(data)}, time)})
}// 获取数组随机项
export const getRandomElement = (arr) => {var randomIndex = Math.floor(Math.random() * arr.length);return arr[randomIndex];
}// 指定范围随机数
export const getRandomNumber = (min, max) => {return Math.floor(Math.random() * (max - min + 1) + min);
}// 节流
export const throttle = (fn, time) => {let timer = nullreturn (...args) => {if (!timer) {timer = setTimeout(() => {timer = nullfn.apply(this, args)}, time)}}
}
// 防抖
export const debounce = (fn, time) => {let timer = nullreturn (...args) => {clearTimeout(timer)timer = setTimeout(() => {fn.apply(this, args)}, time)}
}

data.js 模拟后台返回的数据

import { getRandomElement, getRandomNumber } from "./utils.js"const colorList = ['red', 'blue', 'green', 'pink', 'yellow', 'orange', 'purple', 'brown', 'gray', 'skyblue']export const createList = (pageSize) => {let list = Array.from({ length: pageSize }, (v, i) => i)return list.map(x => {return {background: getRandomElement(colorList),width: getRandomNumber(200, 600),height: getRandomNumber(400, 700),x: 0,y: 0}})
}

瀑布流布局组件waterfall.vue

<template><div class="waterfall-container" ref="containerRef" @scroll="handleScroll"><div class="waterfall-list"><divclass="waterfall-item"v-for="(item, index) in resultList":key="index":style="{width: `${item.width}px`,height: `${item.height}px`,transform: `translate3d(${item.x}px, ${item.y}px, 0)`,}"><slot name="item" v-bind="item"></slot></div></div></div>
</template>
<script setup>
import { ref, onMounted, computed, nextTick, onUnmounted } from "vue";
import { createList } from "@/common/data.js";
import { getRemoteData, throttle, debounce } from "@/common/utils.js";
const props = defineProps({// 间距gap: {type: Number,default: 10,},// 列数columns: {type: Number,default: 3,},// 距离底部bottom: {type: Number,default: 0,},// 分页大小pageSize: {type: Number,default: 10,},
});// 容器ref
const containerRef = ref(null);// 卡片宽度
const cardWidth = ref(0);// 列高度
const columnHeight = ref(new Array(props.columns).fill(0));// 数据list
const resultList = ref([]);// 当前页码
const pageNum = ref(1);// 加载状态
const loading = ref(false);// 计算最小列高度及其下标
const minColumn = computed(() => {let minIndex = -1,minHeight = Infinity;columnHeight.value.forEach((item, index) => {if (item < minHeight) {minHeight = item;minIndex = index;}});return {minIndex,minHeight,};
});// 获取接口数据
const getData = async () => {loading.value = true;const list = createList(props.pageSize);const resList = await getRemoteData(list, 300).finally(() => (loading.value = false));pageNum.value++;resultList.value = [...resultList.value, ...getList(resList)];
};// 滚动到底部获取新一页数据-节流
const handleScroll = throttle(() => {const { scrollTop, clientHeight, scrollHeight } = containerRef.value;const bottom = scrollHeight - clientHeight - scrollTop;if (bottom <= props.bottom) {!loading.value && getData();}
});// 拼装数据结构
const getList = (list) => {return list.map((x, index) => {const cardHeight = Math.floor((x.height * cardWidth.value) / x.width);const { minIndex, minHeight } = minColumn.value;const isInit = index < props.columns && resultList.length <= props.pageSize;if (isInit) {columnHeight.value[index] = cardHeight + props.gap;} else {columnHeight.value[minIndex] += cardHeight + props.gap;}return {width: cardWidth.value,height: cardHeight,x: isInit? index % props.columns !== 0? index * (cardWidth.value + props.gap): 0: minIndex % props.columns !== 0? minIndex * (cardWidth.value + props.gap): 0,y: isInit ? 0 : minHeight,background: x.background,};});
};// 监听元素
const resizeObserver = new ResizeObserver(() => {handleResize();
});// 重置计算宽度以及位置
const handleResize = debounce(() => {const containerWidth = containerRef.value.clientWidth;cardWidth.value =(containerWidth - props.gap * (props.columns - 1)) / props.columns;columnHeight.value = new Array(props.columns).fill(0);resultList.value = getList(resultList.value);
});const init = () => {if (containerRef.value) {const containerWidth = containerRef.value.clientWidth;cardWidth.value =(containerWidth - props.gap * (props.columns - 1)) / props.columns;getData();resizeObserver.observe(containerRef.value);}
};onMounted(() => {init();
});
// 取消监听
onUnmounted(() => {containerRef.value && resizeObserver.unobserve(containerRef.value);
});
</script><style lang="scss">
.waterfall {&-container {width: 100%;height: 100%;overflow-y: scroll;overflow-x: hidden;}&-list {width: 100%;position: relative;}&-item {position: absolute;left: 0;top: 0;box-sizing: border-box;transition: all 0.3s;}
}
</style>

使用该组件(这里columns写死了3列)

<template><div class="container"><WaterFall :columns="3" :gap="10"><template #item="{ background }"><div class="card-box" :style="{ background }"></div></template></WaterFall></div>
</template><script setup>
import WaterFall from "@/components/waterfall.vue";
</script><style scoped lang="scss">
.container {width: 700px;  /* 一般业务场景不是固定宽度 */height: 800px;border: 2px solid #000;margin-top: 10px;margin-left: auto;
}
.card-box {position: relative;width: 100%;height: 100%;border-radius: 4px;
}
</style>

若要响应式调整列数,可参考以下代码


const fContainerRef = ref(null);
const columns = ref(3);
const fContainerObserver = new ResizeObserver((entries) => {changeColumn(entries[0].target.clientWidth);
});// 根据宽度,改变columns列数
const changeColumn = (width) => {if (width > 1200) {columns.value = 5;} else if (width >= 768 && width < 1200) {columns.value = 4;} else if (width >= 520 && width < 768) {columns.value = 3;} else {columns.value = 2;}
};onMounted(() => {fContainerRef.value && fContainerObserver.observe(fContainerRef.value);
});onUnmounted(() => {fContainerRef.value && fContainerObserver.unobserve(fContainerRef.value);
});

瀑布流布局组件监听columns变化

watch(() => props.columns,() => {handleResize();}
);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/479336.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【高阶数据结构】B+树

文章目录 1. B树的概念2. B树的查找3. B-树 VS B树4. B 树的插入分析 1. B树的概念 B树是B树的变形&#xff0c;是在B树基础上优化的多路平衡搜索树&#xff0c;B树的规则跟B树基本类似&#xff0c;但是又在B树的基础上做了一些改进优化。 一棵m阶的B树需满足下列条件&#x…

推荐一个内网穿透工具,支持Windows桌面、Linux、Arm平台客户端

神卓互联是一款常用的内网穿透工具&#xff0c;它可以将本地服务器映射到公网上&#xff0c;并提供域名或子域名给外部访问。神卓互联具有简单易用、高速稳定的特点&#xff0c;支持Windows桌面版、Linux版、Arm版客户端&#xff0c;以及硬件等。 神卓互联内网穿透技术简介 企…

HarmonyOS开发篇—数据管理(分布式数据服务)

分布式数据服务概述 分布式数据服务&#xff08;Distributed Data Service&#xff0c;DDS&#xff09; 为应用程序提供不同设备间数据库数据分布式的能力。通过调用分布式数据接口&#xff0c;应用程序将数据保存到分布式数据库中。通过结合帐号、应用和数据库三元组&#xf…

【力扣 - 二叉树的最大深度】

题目描述 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 提示&#xff1a; 树中节点的数量在 [0, 10^4] 区间内。 -100 < Node.val < 100方法一&#xff1a;深度优先搜索 思路与算法 如…

杨氏矩阵和杨辉三角

杨氏矩阵 有一个数字矩阵&#xff0c;矩阵的每行从左到右是递增的&#xff0c;矩阵从上到下是递增的&#xff0c;请编写程序在这样的矩阵中查找某个数字是否存在。 要求&#xff1a;时间复杂度小于O(N); 分析 若要满足要求时间复杂度小于O(N)&#xff0c;就不能每一行一个个…

7款自媒体人ai写作必备的免费工具,快速高效运营 #AI写作#知识分享#知识分享

在当今信息爆炸的时代&#xff0c;写作成为了人们表达思想、分享知识和传递情感的重要方式之一。对于很多人来说&#xff0c;写作并非易事。我们会陷入困境&#xff0c;无法找到灵感&#xff0c;我们会苦恼于语言表达的准确性&#xff0c;还有时候我们可能遭遇到了创作瓶颈&…

Cesium for Unreal 从源码编译到应用——创建三维地球

一、基础环境 Unreal Engine 5.3 编译好的CesiumForUnreal插件 Cesium ion 账号 二、创建新工程 启动Unreal Engine&#xff0c;选择游戏->空白模板&#xff0c;输入项目名称。 打开内容浏览器&#xff0c;在内容文件夹中新建Maps文件夹&#xff0c;然后在里面添加新的…

Android 沉浸式状态栏

过时的API //设置默认隐藏虚拟按键&#xff0c;虚拟按键显示后为半透明protected open fun hideNavigationBarAndFullScreen() {val flags: Int// This work only for android 4.4flags if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {// This work only for a…

天洑AIFEM软件将助力竞技机器人国际冠军战队再攀高峰

2023年底&#xff0c;烈鹏战队作为中国顶尖机器人队伍代表出征国际赛事Battle of Robots&#xff0c;经过与全球战队激烈竞争&#xff0c;取得国际赛场上5连胜的优秀战绩斩获国际冠军。 天洑智能结构仿真软件AIFEM与玄智科技的技术方案联合&#xff0c;基于烈鹏战队的冠军机器人…

网站常见的反爬手段及反反爬思路

摘要:介绍常见的反爬手段和反反爬思路&#xff0c;内容详细具体&#xff0c;明晰解释每一步&#xff0c;非常适合小白和初学者学习&#xff01;&#xff01;&#xff01; 目录 一、明确几个概念 二、常见的反爬手段及反反爬思路 1、检测user-agent 2、ip 访问频率的限制 …

Spring解决循环依赖

目录 什么是spring循环依赖 什么情况下循环依赖可以被处理&#xff1f; spring 如何解决循环依赖 创建A这个Bean的流程 答疑 疑问&#xff1a;在给B注入的时候为什么要注入一个代理对象&#xff1f; 初始化的时候是对A对象本身进行初始化&#xff0c;而容器中以及注入到B…

信奥一本通:1075:药房管理

这个题可能有点误解&#xff0c;看这个实例&#xff0c;不是用30依次去减10 5 20 6 7 8&#xff0c;如果按照这个减法&#xff0c;30先减10再减5就剩15了&#xff0c;那完全不够后面20减的&#xff0c;所以次数还剩4次。但是&#xff0c;这道题是谁能减就减谁&#xff0c;意思就…