实现思路:Vue 子组件高度不固定下实现瀑布流布局

news2025/2/24 8:05:03

实现思路:Vue 子组件高度不固定下实现瀑布流布局

在这里插入图片描述

一、瀑布流布局基础实现原理

在深入解说不定高度子组件的瀑布流如何实现之前,先大体说一下子组件高度固定已知的这种实现原理:

  1. 有一个已知组件高度的数组。
  2. 定义好这个瀑布流的列数,每列的宽度。
  3. 放置这些子组件的容器设置 position: relative 属性,内部子组件设置 position: absolute 属性,也就是说子组件可以在容器中以 left: --px; top: --px 的方式随意定位。
  4. 依次放置子组件,并记录离顶部最小距离的列数和位置值。下一个子组件的放置位置就是这里。
  5. 按照上面的的操作依次放置数组内所有元素到 dom。

二、我的需求

能看到上面瀑布流的实现前提,是需要每个子组件都有明确固定高度。
而我有一场景是:子组件的高度不能提前知道,它的高度由组件内部的文本多少来决定,它能显示多高就显示多高。
像这种,就需要在渲染过程中去判断最后一个合理的放置位置。

三、子组件动态高度的瀑布流,实现原理

搞了一整天,总算搞出来了,效果还可以。
这个渐进的过程是我添加了一个 timeout 实现的,实际可以更快的刷出来。

在这里插入图片描述


用一句话概括就是:
找到每列中最后可放置位置的 top 值,对比出最小的,作为下一个元素的放置位置。


Vue 实现瀑布流的问题是,Vue 是数据驱动的,就需要在渲染之前就知道每个组件的具体位置。而这,是无法一次性实现的,只能一一去把元素添加了待显示的数组中,当每个元素添加之后,再去计算下一个组件的放置位置。

说一下实现原理,知道原理之后,需要的只是如何实现它。

  1. 定义好你要显示多少列 colCountarrayOrigin 放置原始的数组, arrayShow 用于列表渲染,过程就是将 arrayOrigin 内的元素依次添加到 arrayShow 中,这个过程中去给每个元素添加 top left 位置值
  2. 第一行内部的展示不需要考虑高度值,因为都是 top: 0,放置的时候要标记自己是哪一列,后面会用到。
  3. 依次放置每个子组件到容器中,由于高度是不定的,需要到 nextTick 里面去放置下一个组件,这里可以通过递归的方式去放置,直到元素数量与要放置的元素数量一致。
  4. 后面的只需要查找容器里的最后 colCount + 1 个组件的位置,在每一列中找出每个子组件 offsetTop + offsetHeight 最小值的位置,并标记这个 col 列数,作为放置下一个组件的位置。
  5. 依次执行,直到放完。

在这里插入图片描述

取多少个子组件作为缓存合适?

按照上面的逻辑去实现之后,你会遇到一个新的问题:
在获取容器中最后几个子组件,并获取到每列距离 top 最小的值的时候,可能会略过某列。原因是这个 colCount + 1 的缓存区的数量太小。

像下面这张图一样,如果只取 colCount + 1 个元素的值去计算高度,那么就会忽略前面第二列的高度值。错误的放置在了红色位置。

在这里插入图片描述
原因就是在向后追溯最后 colCount + 1 个元素的时候,这个数量不足以覆盖所有列。如下图,至少需要向上找 13 个元素才可以。
所以我的这个页面中取了上 50 个。

在这里插入图片描述

四、完整代码

看源码吧,这是我在我一个开源项目《标题日记》中实现的一个功能。

github 页面源码: https://github.com/KyleBing/diary/blob/master/src/page/listHole/ListHole.vue
《标题日记》github: https://github.com/KyleBing/diary

主要的代码部分,不完整,完整的请看上面的源码

/**
 * 列表渲染
 */
const diariesShow = ref<Array<DiaryEntityHole>>([])  // 列表展示的日记
const loadGap = 100 // 卡片加载间隔时长,单位 ms
const isShowLoadProcess = true // 是否显示卡片加载的过程


const colCount = 10 // 列数
let lastDiaryIndex = 1  // 最后一个日记的 index
let lastTopPos = 0  // 最后一个日记的末尾位置: 距离 TOP
let lastCol = 0  // 下次该放置的 col index,哪一列
let colWidth = storeProject.insets.windowsWidth / colCount  // 每个元素的宽度

const loadTimeOutHandle = ref()  // 载入过程的 timeOut handle
const isNeedLoadNextTimeout = true  // 是否要打断 timeout 的载入过程

function renderingHoleList(newDiaries: Array<DiaryEntityDatabase>, index: number){
    // 如果不需要载入下面的内容,在 reload 的时候会遇到这种情况
    if (!isNeedLoadNextTimeout){
        return
    }

    // 1. 转成 DiaryEntityHole 对象
    let diary = newDiaries[index] as DiaryEntityHole
    diary.position = {
        top:  lastTopPos,
        left: lastCol * colWidth,
        col: lastCol
    }
    // 2. 添加到展示的列表中
    diariesShow.value.push(diary)

    nextTick(()=>{
        // 3. 待其渲染完成后再去处理下一个
        let domItems = Array.from((document.querySelector('.diary-list-hole') as HTMLDivElement).children) // Elements 转成数组

        // 3.1 第一排,前 colCount 个是不需要知道位置的,因为 top 都为 0
        if (lastDiaryIndex < colCount - 1){
            lastCol = lastCol + 1
            lastTopPos = 0
        }
        // 3.2 以后其它的
        else {
            // 取后 colCount 个元素的 lastTopPos
            let countInDomItems = domItems.length > 50? domItems.slice(domItems.length - 50):domItems
            let domItemsHeightColArray = countInDomItems
                                            .map(item => {
                                                let dom = item as HTMLDivElement
                                                let col = Number(dom.getAttribute('data-col'))
                                                let posTop = dom.offsetTop + dom.offsetHeight
                                                return {
                                                    posTop,
                                                    col
                                                }
                                            })

            // Map 放置第 col 的最大高度值,这里用 Map 或 Set 都可以,反正就是为了使值唯一
            let everyColLastMaxPosMap = new Map()  // [2,345],[3,234],[4,456]
            domItemsHeightColArray.forEach(item => {
                // 获取已经存在的 lastPos
                let existColPos = everyColLastMaxPosMap.get(item.col)
                if (existColPos === undefined){
                    everyColLastMaxPosMap.set(item.col, item.posTop)
                } else {
                    if (item.posTop >= existColPos){ // 如果有更大的,使用最大的
                        everyColLastMaxPosMap.set(item.col, item.posTop)
                    }
                }
            })

            // 将 Map 转成数组
            let everyColLastPosArray: Array<{posTop: number, col: number}> = []
            everyColLastMaxPosMap.forEach((value, key) => {
                everyColLastPosArray.push({
                    posTop: value,
                    col: key
                })
            })
            everyColLastPosArray
                .sort((a,b) => b.col - a.col) // 小值在前
                .sort((a,b) => a.posTop - b.posTop) // 大值在前


            lastTopPos = everyColLastPosArray[0].posTop
            lastCol = everyColLastPosArray[0].col
            // console.log(`${lastDiaryIndex}: `, lastTopPos, lastCol,  everyColLastPosArray, domItemsHeightColArray)
        }

        // 4. index + 1
        index = index + 1
        lastDiaryIndex = lastDiaryIndex + 1

        // 5. 退出递归条件
        if (index < newDiaries.length){
            if (isShowLoadProcess){
                loadTimeOutHandle.value = setTimeout(()=>{
                    renderingHoleList(newDiaries, index)
                }, loadGap)
            } else {
                renderingHoleList(newDiaries, index)
            }
        }
    })
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

新概念英语视频百度云,新概念英语视频百度网盘,新概念1-4册

在现今数字化时代&#xff0c;英语学习资源丰富多样&#xff0c;其中新概念英语视频因其深入浅出的教学风格和丰富多样的学习内容&#xff0c;备受广大英语学习者的青睐。本文旨在为广大英语学习者提供一份详尽的新概念英语视频下载指南&#xff0c;帮助大家轻松获取优质学习资…

Macbook M芯片Homebrew与git的安装与配置

Macbook M芯片Homebrew与git的安装与配置 Homebrew的安装与配置 搜索Homebrew; 找到如下网址https://brew.sh/ 把以上命令复制到终端 执行后&#xff0c;发现并不能下载&#xff1b; 如果你像我一样也是不通的&#xff0c;可以使用国内源,将如下命令复制到终端&#xff1a;…

【C++题解】1750 - 有0的数

问题&#xff1a;1750 - 有0的数 类型&#xff1a;简单循环 题目描述&#xff1a; 请求出 1∼n 中含有数字 0 的数&#xff0c;有多少个&#xff1f; 输入&#xff1a; 一个整数 n&#xff08;n≤999&#xff09; 。 输出&#xff1a; 一个整数&#xff0c;代表 1∼n 中含…

关于Spring Security的CORS

目录 一、CORS是什么 二、同源安全策略 三、Spring Security中CORS的开启 四、其它处理方法 一、CORS是什么 CORS&#xff08;Cross-Origin Resource Sharing&#xff0c;跨源/域资源共享 &#xff09;是一个W3C标准&#xff0c;一种允许当前域&#xff08;domain&#xff…

苹果WWDC揭晓AI系统、电脑等设备系统全线更新,iPhone将接入ChatGPT

iOS 18可自定义主屏幕、可卫星发短信、钱包App新增点击付款Tap to Cash功能、照片App大改、支持RCS短信&#xff1b;iPad首次有原生计算器App&#xff0c;iPad结合Apple Pencil拥有数学笔记功能&#xff1b;新版macOS可通过Mac查看控制iPhone&#xff0c;Safari有AI驱动的高亮显…

Docker:安装 Orion-Visor 服务器运维的技术指南

请关注微信公众号&#xff1a;拾荒的小海螺 博客地址&#xff1a;http://lsk-ww.cn/ 1、简述 Orion-Visor 是一种用于管理和监控容器的工具。它提供了一个直观的界面&#xff0c;用于查看容器的状态、资源使用情况以及日志等信息。在这篇技术博客中&#xff0c;我们将介绍如何…

01.FastLED库基础

FastLED库基础 FastLED库HSV颜色 HSV颜色基本概念 HSV颜色简介 HSV(Hue, Saturation, Value)是根据颜色的直观特性由A. R. Smith在1978年创建的一种颜色表达方法。该方法中的三个参数分别是&#xff1a;色调&#xff08;H&#xff09;&#xff0c;饱和度&#xff08;S&#…

Jenkins三种构建类型

目录 传送门前言一、概念二、前置处理&#xff08;必做&#xff09;1、赋予777权限2、让jenkins用户拥有root用户的kill权限3、要运行jar包端口号需要大于1024 三、自由风格软件项目&#xff08;FreeStyle Project&#xff09;&#xff08;推荐&#xff09;三、Maven项目&#…

最全面又最浅显易懂的Langchain快速上手教程(下)

最全面又最浅显易懂的Langchain快速上手教程&#xff08;下&#xff09; 三. 深入Langchain 1. 架构设计 从上文知道Langchain在架构上使用了从抽象、到具体、再到整合适配的三层架构&#xff0c;这种一层一层逐渐具体的设计最大可能性的保证了架构的可扩展性和维护性。同时…

【Python】 探索 Python 中的 Ellipsis 对象:一个神奇的省略号

基本原理 在 Python 中&#xff0c;Ellipsis 对象是一个特殊的内置对象&#xff0c;它通常用三个连续的点 ... 来表示。这个对象在 Python 中有几个特定的用途&#xff0c;尤其是在切片操作和迭代器表达式中。虽然它看起来像是一个普通的省略号&#xff0c;但它实际上是 Pytho…

DeepSORT(目标跟踪算法)中自由度决定卡方分布的形状

DeepSORT&#xff08;目标跟踪算法&#xff09;中自由度决定卡方分布的形状 flyfish 重要的两个点 自由度决定卡方分布的形状&#xff08;本文&#xff09; 马氏距离的平方在多维正态分布下服从自由度为 k 的卡方分布 独立的信息 在统计学中&#xff0c;独立的信息是指数据…

MySQL的group by与count(), *字段使用问题

文章目录 问题group by到底做了什么举个例子简单来说为什么select字段&#xff0c;count()不能和*共同使用总结 问题 这是一段摘抄自MySQL官网的文字。其大致意思是MySQL拓展了group by的使用&#xff0c;MySQL允许选择没有出现在group by中的字段。换句话说&#xff0c;标准SQ…

覆盖路径规划经典算法 The Boustrophedon Cellular Decomposition 论文及代码详解

2000年一篇论文 Coverage of Known Spaces: The Boustrophedon Cellular Decomposition 横空出世&#xff0c;解决了很多计算机和机器人领域的覆盖路径问题&#xff0c;今天我来详细解读这个算法。 The Boustrophedon Cellular Decomposition 算法详解 这篇论文标题为"C…

【Qt】TreeWidget中Item的UserCheckable注意事项,没有出现多选框

1. 异常 开启 ItemIsUserCheckable以后&#xff0c;界面上没有出现多选框。 QTreeWidgetItem *item new QTreeWidgetItem();item->setText(0, "hello");item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsSelectable |Qt::ItemIsEnabled | Qt::ItemIsAuto…

最新thinkphp5内核全开源女神赢口红H5公众号版第五版(100%可经营)

最新thinkphp5内核全开源女神赢口红H5公众号版第五版&#xff08;100%可经营&#xff09; 搭建教程 1、程序为thinkPHP5开发 php版本要求5.6&#xff01;不支持虚拟主机&#xff01; 2、上传程序到您的根目录&#xff01;导入m213.sql文件&#xff01;修改数据库配置文件app…

Github 2024-06-10 开源项目日报 Top10

根据Github Trendings的统计,今日(2024-06-10统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量C项目2Go项目2PHP项目1Blade项目1TypeScript项目1Lua项目1Dart项目1Swift项目1Cuda项目1Python项目1MDX项目1Ventoy: 100%开源的可启动USB解决方…

考虑双碳分时价格的综合能源系统低碳优化调度

目录 一、主要内容介绍&#xff1a; 二、仿真结果&#xff1a; 三、研究内容&#xff1a; 四、代码下载&#xff1a; 一、主要内容介绍&#xff1a; 在含电热气多种能源的综合能源系统中&#xff0c;复杂的能量转换关系以及可再生能源和负荷的波动性&#xff0c;给综合能源…

必备:产品经理工作文档大全

产品经理&#xff08;英文&#xff1a;Product manager&#xff0c;缩写&#xff1a;PM&#xff09;也称产品企划&#xff0c;是指在公司中针对某项或某类的产品进行规划和管理的人员&#xff0c;主要负责产品的研发、制造、营销、渠道等工作。 产品经理是很难定义的一个角色&a…

群体优化算法---水波优化算法介绍以及应用于聚类数据挖掘代码示例

介绍 水波优化算法&#xff08;Water Wave Optimization, WWO&#xff09;是一种新兴的群智能优化算法&#xff0c;灵感来自水波在自然环境中的传播和衰减现象。该算法模拟了水波在水面上传播和碰撞的行为&#xff0c;通过这些行为来寻找问题的最优解。WWO算法由三种主要的操作…

如何发挥物联网电能表的优势

发挥物联网电能表的优势&#xff0c;对于提升电力系统的智能化水平、优化电力资源配置、提高用电效率以及促进环保发展等方面都具有重要意义。 一、实时监测与数据分析 物联网电能表的核心优势在于其能够实时监测电力使用情况&#xff0c;并通过无线网络将数据传输到云平台。…