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

实现思路: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)
            }
        }
    })
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【回调函数】

1.回调函数是什么&#xff1f; 回调函数就是⼀个通过函数指针调用的函数。 如果你把函数的指针&#xff08;地址&#xff09;作为参数传递给另⼀个函数&#xff0c;当这个指针被用来调用其所指向的函数 时&#xff0c;被调用的函数就是回调函数。回调函数不是由该函数的实现方…

linux的持续性学习

安装php 第一步&#xff1a;配置yum源 第二步&#xff1a;下载php。 yum install php php-gd php-fpm php-mysql -y 第三步&#xff1a;启动php。 systemctl start php-fpm 第四步&#xff1a;检查php是否启动 lsof -i :9000 计划任务 作用&am…

问题:下列关于信息的说法,错误的是( )。 #媒体#知识分享#学习方法

问题&#xff1a;下列关于信息的说法&#xff0c;错误的是&#xff08; &#xff09;。 A. 信息无时不在&#xff0c;无时不有 B. 信息随着时间的推移&#xff0c;效用会越来越小 C. 信息具有可处理性 D. 信息是同一时刻只能被单方使用 参考答案如图所示

pnpm : 无法加载文件 C:\Users\xxxxx\AppData\Roaming\npm\pnpm.ps1,因为在此系统上禁止运行脚本。

vscode中执行pnpm install的时候&#xff0c;直接报了上面的错误。 解决&#xff1a; 然后输入&#xff1a;set-ExecutionPolicy RemoteSigned&#xff0c;按回车&#xff0c;然后根据提示&#xff0c;我们选A。 然后回车。 这样我们再次回到vscode中的我们就会发现可以了。 …

C++:SLT容器-->stack

C:SLT容器--&#xff1e;stack 1. stack容器2. stack 常用接口 1. stack容器 先进后出&#xff0c;后进先出不允许有遍历行为可以判断容器是否为空可以返回元素的个数 2. stack 常用接口 构造函数 stack<T> stk; // stack采用模板类实现&#xff0c;stack对象的默认构造形…

认识Spring 中的BeanPostProcessor

关于BeanPostProcessor和BeanFactoryPostProcessors&#xff0c;将分2篇文章来写&#xff0c;这篇文章是对Spring 中BeanPostProcessor进行了总结 先看下大模型对这个类的介绍&#xff0c;随后再看下这两个类的示例&#xff0c;最后看下这两个类的实现。 这两个类从名字看都很类…

三丰云免费服务器

云网址&#xff1a; https://www.sanfengyun.com 可申请免费云服务器&#xff0c;1核/1G内存/5M宽带/有公网IP/10G SSD硬盘/免备案。 收费云服务器&#xff0c;买2年送1年&#xff0c;有很多优惠

MAVEN架构项目管理工具(下)

1、maven工程约定目录结构 每一个maven在磁盘中都是一个文件夹&#xff08;即项目&#xff0c;以hello项目为例&#xff09; Hello/---/src------/main #放置主程序Java代码和配置文件-----------/java #程序的包和包中的文件-----------/resource #java程…

敏捷风险管理:识别和应对项目威胁与机会

项目风险是一种不确定的事件或条件&#xff0c;一旦发生&#xff0c;就会对项目目标造成积极或消极的影响。 现实中&#xff0c;风险可能是微妙而复杂的&#xff0c;缺少经验的人很难对其进行识别和管理。 敏捷风险管理是敏捷项目治理的基础。在敏捷环境下&#xff0c;敏捷风险…

Spring Boot 项目启动时在 prepareContext 阶段做了哪些事?

概览 如果你对Spring Boot 启动流程还不甚了解&#xff0c;可阅读《Spring Boot 启动流程详解》这篇文章。如果你已了解&#xff0c;那就让我们直接看看prepareContext() 源码。 private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironme…

Knife4j 全局鉴权需求 (在OpenAPI3规范中添加Authorization鉴权请求Header)

文章目录 引言I Knife4j 全局鉴权需求1.1 利用springdoc项目提供的customizer接口解决1.2 常见问题II 添加自定义Header参数(签名字段)see also引言 OpenAPI3规范对于Security的定义说明,主要分为两部分: 在compoents组件下定义Security的鉴权方案类型在接口级别的Operati…

工业机器人远程运维,增强智慧工厂运营管理

1、需求背景 随着工业自动化技术的普及和工业机器人应用的增加&#xff0c;制造业对于生产线稳定性和效率的要求不断提高。然而&#xff0c;传统的现场监控方式存在着地理位置限制、实时监控难度大以及诊断能力有限等问题&#xff0c;迫切需要一种更具灵活性和效率的监控方式。…

这可能是最清晰易懂的 G1 GC 资料

滑动验证页面 概述 G1 (Garbage-First) 于JDK 6u14版本发布&#xff0c;JDK 7u4版本发行时被正式推出&#xff0c;在JDK9时已经成了默认的垃圾回收器&#xff0c;算是CMS回收器的替代 方案&#xff08;CMS在JDK9以后已经废弃&#xff09; G1是一款分代的 (generational)&a…

AI漫画赛道,10分钟快速赚钱秘诀!

AI百宝箱-Chatgpt4.0、Midjourney绘画、人工智能绘画、AI换脸、AI图片放大、AI图片分析、AI图片融合https://h5.cxyhub.com/?invitationhmeEo7 先使用ChatGPT写小说 ComicAI 漫画小说生成网站 1. 创建小说漫画 2. 故事模板 3. 生成角色形…

基于spring boot+MySQL 小区物业管理系统-计算机毕设 附源码37236

spring boot 小区物业管理系统 摘 要 在网络信息的时代&#xff0c;众多的软件被开发出来&#xff0c;给用户带来了很大的选择余地&#xff0c;而且人们越来越追求更个性的需求。在这种时代背景下&#xff0c;小区物业只能以客户为导向&#xff0c;以产品的持续创新作为小区物…

音视频集式分布式拉流管理

一直以来&#xff0c;由于srs zlm等开源软件采用传统直播协议&#xff0c;即使后面实现了webrtc转发&#xff0c;由于信令交互较弱&#xff0c;使得传统的安防监控方案需要在公网云平台上部署大型流媒体服务器&#xff0c;而且节点资源不能统一管理调度&#xff0c;缺乏灵活性和…

问题:小石明知道上课玩手机会影响学习,但就是管不住自己。这说明小石缺乏()教育。 #职场发展#职场发展#经验分享

问题&#xff1a;小石明知道上课玩手机会影响学习&#xff0c;但就是管不住自己。这说明小石缺乏&#xff08;&#xff09;教育。 A.道德情感 B.道德意志 C.道德行为 D.道德认识 参考答案如图所示

B-6 Web应用程序文件包含-Server2233(环境+解析)

B-6 Web应用程序文件包含 任务环境说明:服务器场景名称:Server2233(关闭链接) 通过扫

C++ | Leetcode C++题解之第143题重排链表

题目&#xff1a; 题解&#xff1a; class Solution { public:void reorderList(ListNode* head) {if (head nullptr) {return;}ListNode* mid middleNode(head);ListNode* l1 head;ListNode* l2 mid->next;mid->next nullptr;l2 reverseList(l2);mergeList(l1, l…

Apple WWDC24的18条总结:从GPT-4o开始集成ChatGPT

大家好,我是木易,一个持续关注AI领域的互联网技术产品经理,国内Top2本科,美国Top10 CS研究生,MBA。我坚信AI是普通人变强的“外挂”,所以创建了“AI信息Gap”这个公众号,专注于分享AI全维度知识,包括但不限于AI科普,AI工具测评,AI效率提升,AI行业洞察。关注我,AI之…