当前位置: 首页 > news >正文

Vue3实现小红书瀑布流布局任意组件动态更新页面方法实践

思路

image-20250124101620483

1.首先定义一个瀑布流容器,它的高度暂定(后面会更新)。把需要布局的组件(这里叫做waterfall-item)放在瀑布流容器里面渲染出来。使用绝对定位(position: absolute),把它移到屏幕外面,不要占用页面高度,并且设置不可见(visibility:hidden)。

<div class="waterfall-container" ref="waterfallWrapper" :style="{ height: wrapperHeight + 'px' }"><divv-for="(post, index) in posts":key="post.title"class="waterfall-item"><div class="waterfall-card"><PostCard:title="post.title":content="post.excerpt":time="new Date(post.date).toLocaleDateString()":tag="post.tags ? post.tags.join(', ') : '未分类'":img="post.img":path="`/post/${post.file}`":id="post.title"@imageLoaded="onImageLoaded"/></div></div></div>.waterfall-item {position: absolute;left: 0;top: 0;transform: translate3d(0, 3000px, 0);visibility: hidden;/* 让容器不被空白占位的 trick */
}

2.渲染出来之后才能计算高度。获取.waterfall-item的dom元素,遍历这些元素,使用getBoundingClientRect()获取高度。

3.开始布局。以2列为例,新建两个变量分别表示2列的高度。遍历第2步获取的dom元素的高度,把dom元素的高度加到最小的高度上。这个过程还可以考虑在组件之间加上留白(gutter)。

4.使用transform样式将waterfall-item移动到对应高度所在的位置。transform将组件移动到指定坐标,横坐标跟列有关,纵坐标跟高度有关。

5.更新瀑布流容器的高度为两列高度中最大者。

// 瀑布流容器引用
const waterfallWrapper = ref<HTMLElement | null>(null);
// 瀑布流容器高度
const wrapperHeight = ref(0);// 一些瀑布流配置属性
const gutter = 20;       // 卡片之间的间距,单位px
const cols = ref(2);     // 列数(可根据响应式需求调整)
const colWidth = ref(0); // 每列宽度,或你可以动态计算
const hasAroundGutter = ref(true);
const animationCancel = ref(false);   // 是否取消动画
const posDuration = ref(300);         // 位置动画时长 (ms)/** 2、3、4、5步的布局函数 */
const layoutHandle = async (): Promise<boolean> => {return new Promise((resolve) => {// 初始化 posYinitY();// 获取 .waterfall-item DOM 列表const items: HTMLElement[] = [];if (waterfallWrapper.value) {waterfallWrapper.value.childNodes.forEach((el: any) => {if (el.className === 'waterfall-item') items.push(el);});}if (!items.length) return false;// 遍历每个卡片for (let i = 0; i < items.length; i++) {const curItem = items[i] as HTMLElement;const style = curItem.style;// 最小列const minY = Math.min(...posY.value);const minYIndex = posY.value.indexOf(minY);// 计算 Xconst curX = getX(minYIndex);// 设置 transformstyle.transform = `translate3d(${Math.floor(curX)}px, ${Math.floor(minY)}px, 0)`;style.width = `${colWidth.value}px`;style.visibility = 'visible';// 测量高度const { height } = curItem.getBoundingClientRect();// 输出卡片标题和高度// console.log(`Card "${i}" height: ${height}`);// 更新列高posY.value[minYIndex] += height;// 入场动画(可选)if (!animationCancel.value) {addAnimation(curItem, () => {const time = posDuration.value / 1000;style.transition = `transform ${time}s`;});}}// 容器高度 = 最长列wrapperHeight.value = Math.max(...posY.value);// 等待动画结束setTimeout(() => {resolve(true);}, posDuration.value);});
};/** 初始化 posY */
const initY = () => {posY.value = new Array(cols.value).fill(hasAroundGutter.value ? gutter : 0);
};/** 计算给定列的 X 坐标 */
const getX = (index: number): number => {const count = hasAroundGutter.value ? index + 1 : index;return gutter * count + colWidth.value * index;
};/** 简单的动画函数,给卡片添加class或行内属性 */
function addAnimation(item: HTMLElement, callback?: () => void) {// 也可以从 item.firstChild 取到实际卡片 DOM// 并添加动画class// 这里为简单演示const content = item.firstChild as HTMLElement;if (content) {// 添加一系列动画class/属性content.classList.add('animate__animated', 'animate__fadeIn'); // etc.// 回调if (callback) {setTimeout(() => {callback();}, 300); // 300ms or 你自己的计算}}
}

细节

  • 实际场景中图片一般使用懒加载,这会导致在组件计算高度时因为图片还没有被加载出来,所以得到错误的高度。这时候就需要在组件里添加一个onImageLoaded函数,当图片加载完成后调用,重新布局。
// 图片加载完成后再次布局(可加防抖)
let timer: number | null = null;
function onImageLoaded() {if (timer) clearTimeout(timer);timer = setTimeout(() => {layoutHandle(); // 重新布局}, 100); // 防抖 100ms
}
  • 屏幕尺寸变化时重新布局。设置两个监听器监听屏幕尺寸变化,当屏幕尺寸改变了之后,一个触发宽度重新设置,第二个触发重新布局。
// 定义函数: colWidth = containerWidth - 3 * gutter
function updateColWidth() {const container = waterfallWrapper.value;if (!container) return;// 父容器的实际宽度const containerWidth = container.clientWidth;// 计算后赋值colWidth.value = (containerWidth - 3 * gutter)/2;console.log('containerWidth:', containerWidth);console.log('colWidth:', colWidth.value);
}
onMounted(() => {updateColWidth();window.addEventListener('resize', updateColWidth);window.addEventListener('resize', layoutHandle);
});
  • 防抖,意思就是防止频繁触发布局造成性能爆炸。这个问题可能会在图片非常多,或者屏幕宽度频繁变化时出现。解决思路很简单,就是在所有布局操作前加个定时器,使得在规定时间内只能触发有限次数。代码在细节1有体现。

代码参考:https://github.com/heikaimu/vue3-waterfall-plugin

感谢作者开源。

相关文章:

Vue3实现小红书瀑布流布局任意组件动态更新页面方法实践

思路 1.首先定义一个瀑布流容器&#xff0c;它的高度暂定&#xff08;后面会更新&#xff09;。把需要布局的组件&#xff08;这里叫做waterfall-item&#xff09;放在瀑布流容器里面渲染出来。使用绝对定位&#xff08;position: absolute&#xff09;&#xff0c;把它移到屏幕…...

深度学习项目--基于LSTM的糖尿病预测探究(pytorch实现)

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 LSTM模型一直是一个很经典的模型&#xff0c;一般用于序列数据预测&#xff0c;这个可以很好的挖掘数据上下文信息&#xff0c;本文将使用LSTM进行糖尿病…...

Next.js 实战 (十):中间件的魅力,打造更快更安全的应用

什么是中间件&#xff1f; 在 Next.js 中&#xff0c;中间件&#xff08;Middleware&#xff09;是一种用于处理每个传入请求的功能。它允许你在请求到达页面之前对其进行修改或响应。 通过中间件&#xff0c;你可以实现诸如日志记录、身份验证、重定向、CORS配置、压缩等任务…...

python+playwright自动化测试(四):元素操作(键盘鼠标事件)、文件上传

目录 鼠标事件 悬停 移动 按键 点击 滚轮操作 拖拽 键盘事件 输入文本内容 type输入内容 fill输入内容 按键操作press 文件上传 下拉选/单选框/复选框 滚动条操作 鼠标事件 悬停 page.get_by_text(设置,exactTrue).nth(1).hover() 移动 page.mouse.move(x33…...

【论文+源码】Diffusion-LM 改进了可控文本生成

这篇论文探讨了如何在不重新训练的情况下控制语言模型&#xff08;LM&#xff09;的行为&#xff0c;这是自然语言生成中的一个重大开放问题。尽管近期一些研究在控制简单句子属性&#xff08;如情感&#xff09;方面取得了成功&#xff0c;但在复杂的细粒度控制&#xff08;如…...

双目立体校正和Q矩阵

立体校正 对两个摄像机的图像平面重投影&#xff0c;使二者位于同一平面&#xff0c;而且左右图像的行对准。 Bouguet 该算法需要用到双目标定后外参(R&#xff0c;T) 从上图中可以看出&#xff0c;该算法主要分为两步&#xff1a; 使成像平面共面 这个办法很直观&#xff…...

vscode 自用插件

vscode按住ctrl鼠标左键无法跟踪跳转方法名&#xff0c;装这些插件就可以 vscode-elm-jump:常规的代码跳转定义 Vue CSS Peek:跳转css定义 vue-helper:变量函数只跳转定义 Vetur 代码提示 Baidu Comate 自动帮你写console.log Turbo Console Log: ctrl alt l 选中变量之后&am…...

OpenCV:在图像中添加高斯噪声、胡椒噪声

目录 在图像中添加高斯噪声 高斯噪声的特性 添加高斯噪声的实现 给图像添加胡椒噪声 实现胡椒噪声的步骤 相关阅读 OpenCV&#xff1a;图像处理中的低通滤波-CSDN博客 OpenCV&#xff1a;高通滤波之索贝尔、沙尔和拉普拉斯-CSDN博客 OpenCV&#xff1a;图像滤波、卷积与…...

DuckDB:Golang操作DuckDB实战案例

DuckDB是一个嵌入式SQL数据库引擎。它与众所周知的SQLite非常相似&#xff0c;但它是为olap风格的工作负载设计的。DuckDB支持各种数据类型和SQL特性。凭借其在以内存为中心的环境中处理高速分析的能力&#xff0c;它迅速受到数据科学家和分析师的欢迎。在这篇博文中&#xff0…...

MySQL入门(数据库、数据表、数据、字段的操作以及查询相关sql语法)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…...

kotlin的协程的基础概念

Kotlin的协程是一种用于简化异步编程的强大工具。 理解协程的基础概念可以帮助开发者有效地利用其能力。 以下是Kotlin协程的一些关键基础概念&#xff1a; 协程&#xff08;Coroutines&#xff09; &#xff1a; 协程是一种用于处理并发任务的编程模型&#xff0c;它可以在单…...

Spring--SpringMVC使用(接收和响应数据、RESTFul风格设计、其他扩展)

SpringMVC使用 二.SpringMVC接收数据2.1访问路径设置2.2接收参数1.param和json2.param接收数据3 路径 参数接收4.json参数接收 2.3接收cookie数据2.4接收请求头数据2.5原生api获取2.6共享域对象 三.SringMVC响应数据3.1返回json数据ResponseBodyRestController 3.2返回静态资源…...

隐藏php版本信息x-powered-by

在生产环境中&#xff0c;并不想让别人知道用的是什么版本的php&#xff0c;可以把x-powered-by隐藏掉 在nginx配置文件加上fastcgi_hide_header X-Powered-By; 如下图所示 配置修改后平滑重启nginx...

哈夫曼树(构建、编码、译码)(详细分析+C++代码实现)

D 哈夫曼树 题目要求 编写一个哈夫曼编码译码程序。针对一段文本&#xff0c;根据文本中字符出现频率构造哈夫曼树&#xff0c;给出每个字符的哈夫曼编码&#xff0c;并进行译码&#xff0c;计算编码前后文本大小。 为确保构建的哈夫曼树唯一&#xff0c;本题做如下限定&…...

C++ 二叉搜索树

目录 概念 性能分析 二叉搜索树的插入 二叉树的查找 二叉树的前序遍历 二叉搜索树的删除&#xff08;重点&#xff09; 完整代码 key与value的使用 概念 对于一个二叉搜索树 若它的左子树不为空&#xff0c;则左子树上所有的节点的值都小于等于根节点的值若它的右子树不为空…...

docker构建Java项目镜像常用的Java版本,国内私有仓库公网快速下载,解决从docker.io无法下载的问题

2015工作至今&#xff0c;10年资深全栈工程师&#xff0c;CTO&#xff0c;擅长带团队、攻克各种技术难题、研发各类软件产品&#xff0c;我的代码态度&#xff1a;代码虐我千百遍&#xff0c;我待代码如初恋&#xff0c;我的工作态度&#xff1a;极致&#xff0c;责任&#xff…...

低代码系统-氚云、简道云表单控件对比

组件对比 氚云 简道云 是否都有 1 单行文本 单行文本 ☑️ 2 多行文本 多行文本 ☑️ 3 日期 日期时间 ☑️ 4 数字 数字 ☑️ 5 单选框 单选按钮组 ☑️ 6 复选框 复选框组 ☑️ 7 下拉框 下拉框 ☑️ 8 附件 附件 ☑️ 9 图片 图片 ☑️ 10 地址 地…...

为什么IDEA提示不推荐@Autowired❓️如果使用@Resource呢❓️

前言 在使用 Spring 框架时&#xff0c;依赖注入&#xff08;DI&#xff09;是一个非常重要的概念。通过注解&#xff0c;我们可以方便地将类的实例注入到其他类中&#xff0c;提升开发效率。Autowired又是被大家最为熟知的方式&#xff0c;但很多开发者在使用 IntelliJ IDEA …...

Unity在WebGL中拍照和录视频

原工程地址https://github.com/eangulee/UnityWebGLRecoder Unity版本2018.3.6f1&#xff0c;有点年久失修了 https://github.com/xue-fei/Unity.WebGLRecorder 修改jslib适配了Unity2021 效果图 录制的视频 Unity在WebGL中拍照和录视频...

爬虫基础之爬取某站视频

目标网址:为了1/4螺口买小米SU7&#xff0c;开了一个月&#xff0c;它值吗&#xff1f;_哔哩哔哩_bilibili 本案例所使用到的模块 requests (发送HTTP请求)subprocess(执行系统命令)re (正则表达式操作)json (处理JSON数据) 需求分析: 视频的名称 F12 打开开发者工具 or 右击…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API&#xff0c;用于在函数组件中使用 state 和其他 React 特性&#xff08;例如生命周期方法、context 等&#xff09;。Hooks 通过简洁的函数接口&#xff0c;解决了状态与 UI 的高度解耦&#xff0c;通过函数式编程范式实现更灵活 Rea…...

stm32G473的flash模式是单bank还是双bank?

今天突然有人stm32G473的flash模式是单bank还是双bank&#xff1f;由于时间太久&#xff0c;我真忘记了。搜搜发现&#xff0c;还真有人和我一样。见下面的链接&#xff1a;https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错

出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上&#xff0c;所以报错&#xff0c;到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本&#xff0c;cu、torch、cp 的版本一定要对…...

Caliper 配置文件解析:config.yaml

Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

九天毕昇深度学习平台 | 如何安装库?

pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子&#xff1a; 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

【深度学习新浪潮】什么是credit assignment problem?

Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...

如何通过git命令查看项目连接的仓库地址?

要通过 Git 命令查看项目连接的仓库地址&#xff0c;您可以使用以下几种方法&#xff1a; 1. 查看所有远程仓库地址 使用 git remote -v 命令&#xff0c;它会显示项目中配置的所有远程仓库及其对应的 URL&#xff1a; git remote -v输出示例&#xff1a; origin https://…...

如何做好一份技术文档?从规划到实践的完整指南

如何做好一份技术文档&#xff1f;从规划到实践的完整指南 &#x1f31f; 嗨&#xff0c;我是IRpickstars&#xff01; &#x1f30c; 总有一行代码&#xff0c;能点亮万千星辰。 &#x1f50d; 在技术的宇宙中&#xff0c;我愿做永不停歇的探索者。 ✨ 用代码丈量世界&…...

精益数据分析(98/126):电商转化率优化与网站性能的底层逻辑

精益数据分析&#xff08;98/126&#xff09;&#xff1a;电商转化率优化与网站性能的底层逻辑 在电子商务领域&#xff0c;转化率与网站性能是决定商业成败的核心指标。今天&#xff0c;我们将深入解析不同类型电商平台的转化率基准&#xff0c;探讨页面加载速度对用户行为的…...

spring boot使用HttpServletResponse实现sse后端流式输出消息

1.以前只是看过SSE的相关文章&#xff0c;没有具体实践&#xff0c;这次接入AI大模型使用到了流式输出&#xff0c;涉及到给前端流式返回&#xff0c;所以记录一下。 2.resp要设置为text/event-stream resp.setContentType("text/event-stream"); resp.setCharacter…...