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

news2024/11/19 19:30:13

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

序言

大家好,我是大澈!

本文约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

结语

建立这个平台的初衷:

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

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

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

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

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

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

相关文章

新!2023初中生古诗文大会阅读专辑的考点Word版和在线模拟题来了

如六分成长之前的文章中所述&#xff0c;小学生古诗文大会复选&#xff08;复赛&#xff09;的出题趋势表明&#xff0c;有一些题目出自小学生古诗文阅读专辑&#xff0c;大家纷纷表示考得很细、很难。 所以合理推测&#xff0c;初中生古诗文大会复选&#xff08;复赛&#xf…

如何运行C/C++程序

一、在线运行C/C 码曰 - 让代码在云端多飞一会&#xff1a;这是一个支持C/C&#xff0c;Java&#xff0c;Python等多种语言的在线编程&#xff0c;编译运行&#xff0c;粘贴分享的平台。你可以在这里输入你的代码&#xff0c;点击运行按钮&#xff0c;就可以看到输出结果。你也…

Redis(二):常见数据类型:String 和 哈希

引言 Redis 提供了 5 种数据结构&#xff0c;理解每种数据结构的特点对于 Redis 开发运维⾮常重要&#xff0c;同时掌握每 种数据结构的常⻅命令&#xff0c;会在使⽤ Redis 的时候做到游刃有余。 Redis 的命令有上百种&#xff0c;我们不可能全部死记硬背下来&#xff0c;但是…

9.2 Windows驱动开发:内核解析PE结构导出表

在笔者的上一篇文章《内核特征码扫描PE代码段》中LyShark带大家通过封装好的LySharkToolsUtilKernelBase函数实现了动态获取内核模块基址&#xff0c;并通过ntimage.h头文件中提供的系列函数解析了指定内核模块的PE节表参数&#xff0c;本章将继续延申这个话题&#xff0c;实现…

vue实现动态路由菜单!!!

目录 总结一、步骤1.编写静态路由编写router.jsmain.js注册 2.编写permisstions.js权限文件编写permisstions.jsaxios封装的APIstore.js状态库system.js Axios-APIrequest.js axios请求实例封装 3.编写菜单树组件MenuTree.vue 4.主页中使用菜单树组件 总结 递归处理后端响应的…

java基础-IO

1、基础概念 1.1、文件(File) 文件的读写可以说是开发中必不可少的部分&#xff0c;因为系统会存在大量处理设备上的数据&#xff0c;这里的设备指硬盘&#xff0c;内存&#xff0c;键盘录入&#xff0c;网络传输等。当然这里需要考虑的问题不仅仅是实现&#xff0c;还包括同步…

人工智能|机器学习——机器学习如何判断模型训练是否充分

一、查看训练日志 训练日志是机器学习中广泛使用的训练诊断工具&#xff0c;每个 epoch 或 iterator 结束后&#xff0c;在训练集和验证集上评估模型&#xff0c;并以折线图的形式显示模型性能和收敛状况。训练期间查看模型的训练日志可用于判断模型训练时的问题&#xff0c;例…

IOC DI入门

1.加上Component&#xff0c;控制翻转&#xff0c;将service和dao都交给IOC容器管理&#xff0c;成为IOC容器中的bean。用哪个类就在哪个类上面加component。 2.加上autowired。依赖注入。controller依赖于service&#xff0c;service依赖于dao。加上时&#xff0c;IOC容器会提…

Taro3+Vue3重构Mpvue小程序项目踩坑记

1、Taro小程序编译时报错&#xff1b; 原因:页面中存在小程序识别不了的标签&#xff1b;如div解决方法&#xff1a; 将div标签替换成小程序可识别的标签&#xff1b; 安装Taro中提供的插件:tarojs/plugin-html, 使其可被识别&#xff1b; 插件安装教程参考Taro官网&#xff1…

Matlab 点云曲率计算(之二)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 之前已经讨论过许多关于计算曲率的问题,这里使用一个通过拟合三次曲面方程的方式来计算曲率,计算过程如下图所示: 二、实现代码 %********

springboot+bootstarp+jsp房屋租赁系统ssm_t65a9

小型房屋租赁系统主要有管理员、房东和租户三个功能模块。以下将对这三个功能的作用进行详情的剖析。 管理员模块&#xff1a;管理员是系统中的核心用户&#xff0c;管理员登录后&#xff0c;可以对后台系统进行管理。主要功能有个人中心、房东管理、租户管理、房源城市管理、房…

数据库应用:MongoDB 库与集合管理

目录 一、理论 1.MongoDB用户管理 2.MogoDB库管理 3.MogoDB集合管理 二、实验 1.MongoDB用户管理 2.MogoDB库管理 3.MogoDB集合管理 三、问题 1.不显示新创建的数据库 2.插入数据报错 3.删除指定数据库报错 一、理论 1.MongoDB用户管理 (1) 内置角色 数据库用户…

什么是高级语言、机器语言、汇编语言?什么是编译和解释?

1、高级语言 计算机程序是一种让计算机执行特定任务的方法。程序是由程序员用一种称为编程语言的特殊语言编写的。编程语言有很多种&#xff0c;例如 C、C、Java、Python 等。这些语言被称为高级语言&#xff0c;因为它们更接近人类的自然语言&#xff0c;而不是计算机能够直接…

【LeetCode刷题】--38.外观数列

38.外观数列 方法&#xff1a;遍历生成 该题本质上是依次统计字符串中连续相同字符的个数 例如字符串 1112234445666我们依次统计连续相同字符的个数为: 3 个连续的字符 1, 222 个连续的 2&#xff0c;1 个连续的字符 3&#xff0c;3个连续的字符 4&#xff0c;1个连续的字符…

创建一个带有背景图层和前景图层的渲染窗口

开发环境&#xff1a; Windows 11 家庭中文版Microsoft Visual Studio Community 2019VTK-9.3.0.rc0vtk-example demo解决问题&#xff1a; 创建一个带有背景图层和前景图层的渲染窗口&#xff0c;知识点&#xff1a;1. 画布转image&#xff1b;2. 渲染图层设置&#xff1b;3.…

如何高效批量生成条形码?

条形码作为商品、库存和信息管理的基础工具&#xff0c;扮演着至关重要的角色。为了满足用户对于高效、专业、多样化的条形码生成需求&#xff0c;我们推出了一款专业高效的在线条形码生成工具。 网址&#xff1a;https://www.1txm.com/ 多样化条形码支持 易条形支持多种常见…

Django请求生命周期流程

浏览器发起请求。 先经过网关接口&#xff0c;Django自带的是wsgiref&#xff0c;请求来的时候解析封装&#xff0c;响应走的时候打包处理&#xff0c;这个wsgiref模块本身能够支持的并发量很少&#xff0c;最多1000左右&#xff0c;上线之后会换成uwsgi&#xff0c;并且还会加…

Redis 主库挂了,如何不间断服务?

目录 1、哨兵机制的基本流程 2、主观下线和客观下线 3、如何选定新的主库&#xff1f; 总结 // 你只管前行&#xff0c;剩下的交给时间 在 reids 主从库集群模式下&#xff0c;如果从库发生故障了&#xff0c;客户端可以继续向主库或其他从库发送请求&#xff0c;进行相关的…

宠物网站的技术 SEO:完整指南

您是宠物行业网站的从业者吗&#xff1f;那么您一定知道&#xff0c;当人们寻找与宠物相关的资源时&#xff0c;在搜索引擎结果中排名靠前有多么重要。 这就是技术SEO的用武之地&#xff01;它正在调整您网站的后端代码和服务器配置&#xff0c;以在 SERP 中排名更高。 在此&…

PCF8591多通道数据读取异常问题

问题描述 PCF8591在循环读取两个通道时&#xff0c;两个通道数据出现交错问题。 例如我们想实现&#xff1a;第一次读取通道一、第二次读取通道二、第三次读取通道一、第四次读取通道二……依次循环 但实际数据&#xff1a;第一次读取的值为0x80、第二次读取的值为通道一的值、…