CSS问题:如何实现瀑布流布局?

前端功能问题系列文章,点击上方合集↑

序言

大家好,我是大澈!

本文约2500+字,整篇阅读大约需要4分钟。

本文主要内容分三部分,如果您只需要解决问题,请阅读第一、二部分即可。如果您有更多时间,进一步学习问题相关知识点,请阅读至第三部分。

感谢关注微信公众号:“程序员大澈”,然后加入问答群,从此让解决问题的你不再孤单!

1. 需求分析

在电商前台项目中,使用瀑布流的布局形式,展示商品图片列表。

让用户在浏览商品列表时,总有商品图片看不全,使用户可以无限滚动地查看列表下面的其它商品内容。

进而提高用户浏览量,增加商品曝光度,最终提升用户购买几率。

2. 实现步骤

2.1 什么是瀑布流布局呢

瀑布流布局,是页面上是一种参差不齐的多栏布局。

随着页面滚动条向下滚动,这种布局会不断加载新的数据内容,并附加至当前高度最低列的尾部。

它的特点是:布局宽度一致,高度不一致,上下错落排列。一般用于图片内容的展示。

2.2 代码实现

模版代码:

一行要展示几条数据,就定义几个column元素。这里以三列为例。

<template>
  <div class="page-main">
    <div class="card">
      <div class="coloum1">
        <div
          class="card-item"
          v-for="(item, index) in cardList1"
          :key="index"
          :style="[
            { background: item.color },
            { height: item.height },
            { lineHeight: item.height },
          ]"
          :class="{ visibles: isVisibility }"
        >
          <p class="text">{{ item.num }}</p>
        </div>
      </div>
      <div class="coloum2">
        <div
          class="card-item"
          v-for="(item, index) in cardList2"
          :key="index"
          :style="[
            { background: item.color },
            { height: item.height },
            { lineHeight: item.height },
          ]"
          :class="{ visibles: isVisibility }"
        >
          <p class="text">{{ item.num }}</p>
        </div>
      </div>
      <div class="coloum3">
        <div
          class="card-item"
          v-for="(item, index) in cardList3"
          :key="index"
          :style="[
            { background: item.color },
            { height: item.height },
            { lineHeight: item.height },
          ]"
          :class="{ visibles: isVisibility }"
        >
          <p class="text">{{ item.num }}</p>
        </div>
      </div>
    </div>
  </div>
</template>

逻辑代码:

第一次渲染时,先把已有数据按顺序正常展示。

然后利用nextTick钩子,在第二次渲染时,先获取所有元素,再循环遍历所有元素,再从第二行第一个元素开始,计算每一列高度和的最小值,把新数据放到最小高度列的数组数据中。以此类推,判断完所有已获取元素。

这里在两次渲染之间,可能会出现页面闪烁现象,所以做了元素显示隐藏的样式处理。

再就是,有多少列,则定义多少新的空数组。这里以三列为例。

<script setup>
import { ref, onMounted, reactive, nextTick } from "vue";

// 展示数据
const cardList = reactive([
  {
    num: "1号 100px",
    color: "#3498db",
    height: "100px",
  },
  {
    num: "2号 200px",
    color: "#2ecc71",
    height: "200px",
  },
  {
    num: "3号 60px",
    color: "#27ae60",
    height: "60px",
  },
  {
    num: "4号 80px",
    color: "#e67e22",
    height: "80px",
  },
  {
    num: "5号 60px",
    color: "#e74c3c",
    height: "60px",
  },
  {
    num: "6号 200px",
    color: "#7f8c8d",
    height: "200px",
  },
]);

// 由于渲染时候对数据的两次赋值,则会出现一次闪烁,需要做显示隐藏处理
const isVisibility = ref(true);

onMounted(() => {
  // 第一次渲染赋值
  equallyCard();

  // 第二次渲染赋值
  nextTick(() => {
    caLFlex();
  })
  .then(() => {
    // 闪烁显示隐藏处理
    isVisibility.value = true;
  });
});

// 各列的展示数据
const cardList1 = ref([]);
const cardList2 = ref([]);
const cardList3 = ref([]);

// 第一次渲染赋值
function equallyCard() {
  // 平分3列的数据,确保页面上遍历卡片dom的真实顺序与平分的一致
  let num = parseInt(cardList.length / 3);

  cardList.forEach((item, index) => {
    if (index < num) {
      cardList1.value.push(item);
      return;
    }
    if (index < 2 * num) {
      cardList2.value.push(item);
      return;
    }
    cardList3.value.push(item);
  });
}

// 第二次渲染赋值
function caLFlex() {
  let arr1 = []; // 第一列的新值
  let arr2 = []; // 第二列的新值
  let arr3 = []; // 第三列的新值
  let heightArry_1 = []; // 第一列的卡片高度
  let heightArry_2 = []; // 第二列的卡片高度
  let heightArry_3 = []; // 第三列的卡片高度

  Array.from(document.querySelectorAll(".card-item")).forEach((item, index) => {
    // 第一行中的元素无需判断,直接加到新数组中
    if (index === 0) {
      heightArry_1.push(item.offsetHeight);
      arr1.push(cardList[index]);
      return;
    }
    if (index === 1) {
      heightArry_2.push(item.offsetHeight);
      arr2.push(cardList[index]);
      return;
    }
    if (index === 2) {
      heightArry_3.push(item.offsetHeight);
      arr3.push(cardList[index]);
      return;
    }


    // 计算每一列高度
    const heightTotal_1 = heightArry_1.length
      ? Array.from(heightArry_1).reduce((accumulator, currentValue) => accumulator + currentValue)
      : 0; // 第一列的总高度
    const heightTotal_2 = heightArry_2.length
      ? Array.from(heightArry_2).reduce((accumulator, currentValue) => accumulator + currentValue)
      : 0; // 第二列的总高度
    const heightTotal_3 = heightArry_3.length
      ? Array.from(heightArry_3).reduce((accumulator, currentValue) => accumulator + currentValue)
      : 0; // 第三列的总高度


    // 找到高度最小值,并在最小高度新数组中添加新数据
    let minNumber = Math.min(heightTotal_1, heightTotal_2, heightTotal_3);
    switch (minNumber) {
      case heightTotal_1:
        heightArry_1.push(item.offsetHeight);
        arr1.push(cardList[index]);
        break;
      case heightTotal_2:
        heightArry_2.push(item.offsetHeight);
        arr2.push(cardList[index]);
        break;
      case heightTotal_3:
        heightArry_3.push(item.offsetHeight);
        arr3.push(cardList[index]);
        break;
    }
  });

  // 重新将数据赋值给各列数组
  cardList1.value = arr1;
  cardList2.value = arr2;
  cardList3.value = arr3;
}
</script>

样式代码:

使用了flex布局来做行的排版。这里根据个人项目实际需求自定义即可。

<style lang="scss" scoped>
.page-main {
  background: #ffffff;
  height: 100vh;
  overflow: hidden;
  padding: 0 30px;
  .card {
    display: flex;
    flex-direction: row;
    justify-content: space-around;
    .card-item {
      visibility: hidden;
      margin-bottom: 20px;
      text-align: center;
      width: 216px;
      border-radius: 16px;
    }
    .visibles {
      visibility: visible;
    }
  }
}
</style>

3. 问题详解

3.1 关于NextTick的个人拙见

作用:等待DOM更新后,再执行内部传入的回调函数

使用场景:  

  • created中想要获取DOM

  • 响应式数据变化后获取DOM更新后的状态,如 获取列表更新后的高度

原理: 把nextTick回调方法放在renderWatcher回调之后执行,这样就能拿到更新后的DOM

3.2 瀑布流其它实现方式

关于瀑布流的实现方式,网上真的是五花八门,各种方法都有。

但因为精力有限,其它方式我也没有再去尝试,只挑选了这么一种比较常用的实现方式,也就是flex布局+js动态计算列高度的方式。我觉的这种方式就足够了,尝试用着还不错。

当然,本次实现的代码,不会是大澈个人空想而来,一定是站在了某位大佬的肩膀之上,又加上了一些个人的理解和拙见,才分享给了朋友们。

最后,也放上参考大佬的文章地址,大佬各种实现方式讲的挺全的,供大家参考:http://d5rhe.jbdi.cn/7b

结语

建立这个平台的初衷:

  • 打造一个仅包含前端问题的问答平台,让大家高效搜索处理同样问题。

  • 通过不断积累问题,一起练习逻辑思维,并顺便学习相关的知识点。

  • 遇到难题,遇到有共鸣的问题,一起讨论,一起沉淀,一起成长。

感谢关注微信公众号:“程序员大澈”,然后加入问答群,从此让解决问题的你不再孤单!

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

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

相关文章

HarmonyOS ArkTS 使用DevEco Studio高效开发(十三)

1、快速开始 打开IDE后&#xff0c;在IDE上边栏有个Help入口&#xff0c;里面有一个Quick Start快速开始入口&#xff0c;点击进去就会进入到快速开始面板。在这个面板中会有一些快速入门的实验指导和一些常用的链接。快速开始相当于一个收藏夹&#xff0c;把最常用的一些学习…

经济观察与ChatGPT聊了聊 :OpeoAI 的144个小时到底发生了什么

本心、输入输出、结果 文章目录 经济观察与ChatGPT聊了聊 &#xff1a;OpeoAI 的144个小时到底发生了什么前言感恩节&#xff1a;奥特曼在社交媒体上发文&#xff1a;和Quora CEO亚当德安杰洛&#xff08;Adam DAngelo&#xff09;度过了美好的几个小时对话ChatGPT 探寻技术发展…

还在担心发抖音没素材跟文案?[腾讯云HAI] AIGC带你蹭热度“今年你失去了什么?”

目录 &#x1f433;前言&#xff1a; &#x1f680;了解高性能应用服务 HAI &#x1f47b;即插即用 轻松上手 &#x1f47b;横向对比 青出于蓝 &#x1f424;应用场景-AI作画 &#x1f424;应用场景-AI对话 &#x1f424;应用场景-算法研发 &#x1f680;使用HAI进行…

【Python小游戏】推荐8款自由的Python游戏项目

推荐8款自由的Python游戏项目 今天给大家推荐8款不错的Python小游戏&#xff0c;这些小游戏所有项目文件&#xff08;包括所需的所有代码、图像和音频文件&#xff09;&#xff0c;给大家已经放到平台的下载频道&#xff0c;需要的可以注意一下文末的链接地址。 下面给大家简单…

微服务--05--配置管理

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 配置管理这些问题都可以通过统一的配置管理器服务解决。而Nacos不仅仅具备注册中心功能&#xff0c;也具备配置管理的功能&#xff1a; 1.配置共享1.1.添加共享配置…

【Linux】基本指令(二)

本文续接上文基本指令&#xff08;一&#xff09; 目录 cpmvcatmore && less cp 语法&#xff1a;cp [选项] 源文件或目录 目标文件或目录 功能: 复制文件或目录 说明: cp指令用于复制文件或目录&#xff0c;如同时指定两个以上的文件或目录&#xff0c;且最后的目的地…

深入Android S (12.0) 探索Framework之输入系统IMS的构成与启动

文章目录 前言一、输入系统的基本组成部分二、输入系统相关源码分析1、IMS 构建1.1、SystemServer # startOtherServices()1.2、InputManagerService1.3、NativeInputManager # nativeInit()1.4、NativeInputManager1.5、InputManager1.6、InputDispatcher1.7、InputReader1.8、…

CodeTON Round #7 (Div. 1 + Div. 2)

A.jagged Swaps 题意&#xff1a; 给出一个包含 n n n个数字的序列&#xff0c;每次可以选择一个同时大于左右两边相邻的数字&#xff0c;将这个数字与它右边的数字交换&#xff0c;问能否在经过若干次操作后使序列变为升序。 分析&#xff1a; 由于交换只能向后进行&#…

神经网络:脑科学中功能MRI成像的应用及其一些相关概念

文章目录 一、MRI成像简介核磁共振成像&#xff08;MRI&#xff09;侵入式成像功能磁共振成像&#xff08;fMRI&#xff09;血氧水平依赖&#xff08;BOLD&#xff09;效应对比基线状态代理指标 二、fMRI具有延迟性及其解决方案原因解决方法 三、fMRI 数据处理1. 数据预处理2. …

YOLOv5算法进阶改进(5)— 主干网络中引入SCConv | 即插即用的空间和通道维度重构卷积

前言:Hello大家好,我是小哥谈。SCConv是一种用于减少特征冗余的卷积神经网络模块。相对于其他流行的SOTA方法,SCConv可以以更低的计算成本获得更高的准确率。它通过在空间和通道维度上进行重构,从而减少了特征图中的冗余信息。这种模块的设计可以提高卷积神经网络的性能。�…

23种设计模式之C++实践

23种设计模式之C++实践 1. 简介2. 基础知识3. 设计模式(一)创建型模式1. 单例模式1.2 饿汉式单例模式1.3 懒汉式单例模式比较IoDH单例模式总结2. 简单工厂模式简单工厂模式总结3. 工厂方法模式工厂方法模式总结4. 抽象工厂模式抽象工厂模式总结5. 原型模式原型模式总结6. 建造…

Django大回顾-2 之 Django的基本操作、路由层,MTV和MVC模型

【1】MTV和MVC模型 MVC与MTV模型 --->所有web框架其实都遵循mvc架构 MVC模型 MVC 本来坨在一起的代码&#xff0c;拆到不同的位置 模型(M&#xff1a;数据层)&#xff0c;控制器(C&#xff1a;逻辑判断)和视图(V&#xff1a;用户看到的)三层 他们之间以一种插件式…

C++ 背包理论基础01 + 滚动数组

背包问题的重中之重是01背包 01背包 有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品只能用一次&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 每一件物品其实只有两个状态&#xff0c;取或者不…

sprintboot快速初始化【Springboot】

1.首先选择创建项目 2.填写对应的项目信息 一定要勾选maven&#xff0c;否则没有pom文件&#xff0c;选择next 3.选择应用场景 点击 create&#xff0c;DIEA就会根据你的选择自动创建项目骨架&#xff1b; 4.创建一个控制层 随便创建一个控制层&#xff0c;测试一下项目是否…

智能优化算法应用:基于阴阳对算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于阴阳对算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于阴阳对算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.阴阳对算法4.实验参数设定5.算法结果6.参考文献7.…

js相同字符串截取拼接

原数据 const list [999-1234567801,999-1234567802,999-1234567803, ]; const list1 [999-1234567899,999-1234567900,999-1234567901, ];期望数据 999-1234567801/2/3 //list 999-1234567899/900/901 //list1处理代码 // 连续号码处理 export const formatNumber (tick…

Python实现性能自动化测试

一、思考❓❔ 1.什么是性能自动化测试? 性能 系统负载能力超负荷运行下的稳定性系统瓶颈自动化测试 使用程序代替手工提升测试效率性能自动化 使用代码模拟大批量用户让用户并发请求多页面多用户并发请求采集参数&#xff0c;统计系统负载能力生成报告 2.Python中的性能自动…

Python 装饰器与偏函数

目录 装饰器 概念 简单的装饰器 复杂点的装饰器 通用装饰器 定义通用装饰器 使用装饰器 偏函数 引入类库 应用 总结 装饰器 概念 装饰器就是个闭包&#xff1b;把一个函数当做参数&#xff0c;返回一个修改过功能的函数&#xff1b; 本质上是一个返回函数的函数。…

堆排序(详解)

在上篇文章中&#xff0c;我们说利用堆的插入和删除也可以排序数据&#xff0c;但排序的只是堆里面的数组&#xff1b;同时每次排序数据都要单独写一个堆的实现&#xff0c;很不方便&#xff0c;这次就来着重讲讲如何使用堆排序。 1.建堆 给了你数据&#xff0c;要利用堆对数据…

echarts修改tooltip默认的圆点图标为其他样式

业务需求&#xff0c;默认是圆点&#xff0c;需要把线的由圆点改为线 红色线是理论&#xff0c;点是历史理论&#xff0c;绿色线是实际&#xff0c; 点是历史实际&#xff0c;在series里的顺序也是这样排的。 打印出来的params里的marker就是圆点&#xff0c;改这段代码就可以了…