首页IT科技瀑布流式布局怎么实现(实现瀑布流布局的四种方法)

瀑布流式布局怎么实现(实现瀑布流布局的四种方法)

时间2025-05-06 09:19:29分类IT科技浏览4178
导读:一、 什么是瀑布流布局 1.是什么 页面上是一种 参差不齐 的多栏布局,类似上图所示随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部,大部分为图片,图片 固定 宽度,高度 不一,根据原比例缩放到宽度达到固定的要求,每行排满后...

一           、 什么是瀑布流布局

1.是什么

页面上是一种 参差不齐 的多栏布局            ,类似上图所示随着页面滚动条向下滚动                 ,这种布局还会不断加载数据块并附加至当前尾部      ,大部分为图片            ,图片 固定 宽度                 ,高度 不一      ,根据原比例缩放到宽度达到固定的要求      ,每行排满后                 ,新的图片添加到后面

2.特点

固定宽度           ,高度不一 岑参不齐的布局 以图片为主

二                  、有什么优缺点

1.优点

节省空间:降低页面的复杂 对于 触屏设备非常友好:通过向上滑动浏览      ,交互方式更符合直觉 良好的视觉体验:浏览时不会被页面整整齐齐的高度影响                  ,参差不齐           ,降低浏览的疲劳

2.缺点

内容总长度 无法掌握 数据过多时,容易造成页面 加载的负荷 再次加载时 很难定位上一次浏览的内容

三      、实现方法

法一     、纯css的写法:【multi-column 多栏布局】

1.两个重要属性

column-count : 定义列数

column-gap :列与列之间的间隔

2.特点

顺序只能是 从上到下                  , 再左到右

3.缺点

由于排列顺序是先 从上到下                 , 再左到右,只能用于数据固定            , 无法动态的加载追加                 ,对于滚动到底部加载新数据则无法实现            。

4.注意点:

有时候页面会出现在前几列的最后一个元素的内容被自动断开      ,一部分在当前列尾            ,一部分在下一列的列头                 。这时候子元素可以用 break-inside设置为不被截断 avoid来控制

break-inside: avoid; // 不被截断 默认值是auto                 ,会被截断

5.实现的代码模式(以下用vue3.0来书写)

template <template> <div class="page-main"> <div class="card"> <div class="card-item" v-for="(item,index) in cardList" :key="index" :style="[{background: item.color},{height: item.height},{lineHeight: item.height}]"> <p class="text">{{index}}</p> </div> </div> </div> </template> script <script setup> import {ref} from vue const cardList = ref([ // 模拟数据 { color: #FCCF0A, height: 100px, }, ...... ]) </script> style multi-column 实现 <style lang="scss" scoped> .page-main{ background: #ffffff; min-height:100vh; padding: 0 30px; .card{ column-count: 3; // 定义三列 column-gap: 20px; // 列与列的距离为20px .card-item{ text-align: center; width: 216px; border-radius: 16px; grid-row-start: auto; margin-bottom: 20px; break-inside: avoid; // 不被截断 } } } </style>

法二                  、泳道的思想:【用flex弹性布局+计算元素高度实现布局】

泳道的概念:通俗的说是类似泳道一样      ,先设置泳道列数      ,一列一列的                 ,然后往里加东西           ,就自动往下面走了      。

1.原理的分析

适用范围:基本业务都能实现 首先比如有四个元素了      ,并且设置四列 那么第五个位置在哪里呢 答案: 是下面图的位置上                  ,找到的位置应该高度为所有列中最小的位置

第六个的位置呢           ,答案还是和上面一样的所示 找到高度为所有列中最小的位置,则为下面的位置

2.算法思路:通过上面的分析则能了解瀑布流的思路了

设计要分成的列数 设置每列的 宽度一致 每次插入的位置选择所有列高度最小 的位置                  ,依次循环

3.代码实现思路

由于需要拿到每个数据的dom元素的实际值                 ,则需要先对数据进行赋值,这样就可以拿到所有的元素dom上的高度 则渲染时候对数据的两次赋值            ,则会出现一次闪现                 ,需要防抖       ,可以先将卡片设置为visibility:hidden             ,当后面对数据处理完                 ,重新渲染后再将卡片设置为visibility:visible 多少列则定义多少新的空数组      ,然后根据瀑布流的思路依次插入 到空的数组即可      ,最后再重新渲染页面即可

4.代码实现(以下用vue3.0来实现)

template (例子为三列) <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> js <script setup> import {ref,onMounted, reactive,nextTick} from vue const cardList = reactive([ // 测试数据 { num: 0, color: #FCCF0A, height: 100px, }, ...测试数据 ]) const isVisibility = ref(true) // 由于渲染时候对数据的两次赋值                 ,则会出现一次闪现           ,需要防抖 onMounted(()=>{ equallyCard() nextTick(()=>{ caLFlex() }).then(()=>{ isVisibility.value = true }) }) const cardList1 = ref([]) // 各列的数据 const cardList2 = ref([]) const cardList3 = ref([]) function equallyCard(){ // 平分数据      ,确保页面上遍历卡片dom的真实顺序与平分的一致 document.querySelectorAll(.card-item)) let num = parseInt(cardList.length/3) cardList.forEach((item,index)=>{ if(index<num){ cardList1.value.push(item) return } if(index<2*num){ cardList1.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(( acc, cur ) => acc + cur) : 0 // 第一列的总高度 const heightTotal_2 = heightArry_2.length ? Array.from(heightArry_2).reduce(( acc, cur ) => acc + cur) : 0 // 第二列的总高 const heightTotal_3 = heightArry_3.length ? Array.from(heightArry_3).reduce(( acc, cur ) => acc + cur) : 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 } css <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>

法三            、绝对定位实现:【精确计算每个子元素绝对定位到瀑布流它应该去的地方           ,需要后期一些优化,并不推荐使用】

1.缺点

计算量相比较 大                  ,较复杂 会有高度塌陷问题 子元素因为设置了 absolute并不会占高                 ,页面可滚动的话又会产生另外的问题 如果在移动端中会做适配,当前的绝对定位的高度单位在代码实现若为px 并不会自动换算 当视口的窗口resize改变            ,需要重新计算元素的位置                 ,若会不断触发事件      ,性能消耗大            ,加载也慢                 ,这是不可取的

2.实现的原理

通过>精准的计算每个子元素的位置      ,定位到瀑布流应该去的地方

3.代码实现思路

子元素全部设置成绝对定位 找到所有列中>最小的位置 计算子元素定位时的 top 以及left 修改子元素的样式      ,设置position为absolute ,以及设置top ,left 每次插入的位置选择所有列高度最小 的位置                 ,依次循环

4.代码实现(vue3.0来实现)

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

以下是一些代码说明

1     、 coloumHight [0,0] 为两列           ,coloumHight [0,0,0] 为三列

2                 、 getMinColoumHeight方法: 找到最小列 <script setup> import {onMounted, reactive, ref} from vue const cardList = reactive([ // 测试数据 { num: 0, color: #FCCF0A, height: 100px, }, ...测试数据 ]) const isVisibility = ref(false) // 由于渲染时候对数据的两次赋值      ,则会出现一次闪现                  ,需要防抖 onMounted(()=>{ caLFlex() }) function caLFlex(){ let coloumHight = [0,0,0] // 每列元素的高度           ,本例子为3列 // 依次插入每个元素 Array.from(document.querySelectorAll(.card-item)).forEach((item,index) =>{ let coloum = getMinColoumHeight(coloumHight) // 得到当前所有最小高度的一列 let itemTop = coloumHight[coloum] let itemLeft = coloum * 200 item.style.position = "absolute" item.style.top = `${itemTop}px` item.style.left = `${itemLeft}px` // 当前高度加上新增的元素高度 coloumHight[coloum] += item.offsetHeight }) isVisibility.value = true } // 找到所有列中高度最小的一列 function getMinColoumHeight(arr){ let min = Math.min(...arr) return arr.indexOf(min) !== -1 ? arr.indexOf(min) : 0 // 默认第一列 } </script> style <style lang="scss" scoped> .page-main{ background: #ffffff; height:100vh; overflow: hidden; padding: 0 30px; .card{ position: relative; .card-item{ width: 200px; text-align: center; visibility: hidden; } .visibles { visibility: visible; } } } </style>

法四            、插件实现:【用插件vue-waterfall2 不是很建议使用,一般用于图片的加载                  ,但是复杂的业务就不是很ok了】

1.安装依赖

npm i vue-waterfall2@latest --save

2.引入

import waterfall from vue-waterfall2 Vue.use(waterfall)

3.相关api

4.代码实现

<template> <div class="container-water-fall"> <waterfall :col="col" :data="data" @scroll="scroll" @finish="finish" :height="100vh" > <template> <div class="cell-item" v-for="(item, index) in data" :key="index" > <img v-if="item.img" :src="item.img" alt="加载错误" /> <div class="item-body"> <div class="item-desc">{{ item.title }}</div> <div class="item-footer"> <div v-if="item.avatar" class="avatar" :style="{ backgroundImage: `url(${item.avatar})`, }" ></div> <div class="name">{{ item.user }}</div> <div class="like" :class="item.liked ? active : " > <i></i> <div class="like-total">{{ item.like }}</div> </div> </div> </div> </div> </template> </waterfall> </div> </template> <script> import json from "./components/data.json"; export default { name: "App", /* 注意: 1. gutterWidth需要与width一起使用才会生效                 ,否则会进行自适应宽度(使用rem布局时,需先计算出自适应后的宽度再传值) 2. 使用了`waterfall`的父组件,如果样式存在问题            ,可去掉css `scoped`尝试一下 */ data() { return { data: [ { img: "https://www.yuucn.com/wp-content/uploads/2023/04/1682160495-c13028c42f0acdf.png?w=377&h=451&x-oss-process=image/resize,w_1080", avatar: "https://www.yuucn.com/wp-content/uploads/2023/04/1682160502-c13028c42f0acdf.jpg@80w_80h_90q_1e_1c_1x.jpg", title: "最近浴室新宠                 ,日系神仙好物了解一下~", user: "迷人的小妖精迷人的小妖精", like: "953", }, { img: "https://www.yuucn.com/wp-content/uploads/2023/04/1682160508-c13028c42f0acdf.jpg?w=720&h=960&x-oss-process=image/resize,w_1080", avatar: "https://www.yuucn.com/wp-content/uploads/2023/04/1682160502-c13028c42f0acdf.jpg@80w_80h_90q_1e_1c_1x.jpg", title: "真的是万能.超级实用.包包必备单品! ! !", user: "迷人的小妖精迷人的小妖精", like: "952", }, { img: "https://www.yuucn.com/wp-content/uploads/2023/04/1682160515-c13028c42f0acdf.jpg?w=712&h=534&x-oss-process=image/resize,w_1080", avatar: "https://www.yuucn.com/wp-content/uploads/2023/04/1682160502-c13028c42f0acdf.jpg@80w_80h_90q_1e_1c_1x.jpg", title: "150元搞定全套护肤品      ,这些护肤好物必须交出来!", user: "迷人的小妖精迷人的小妖精", like: "953", }, { img: "https://www.yuucn.com/wp-content/uploads/2023/04/1682160521-c13028c42f0acdf.jpg?x-oss-process=image/resize,w_1080", avatar: "https://www.yuucn.com/wp-content/uploads/2023/04/1682160502-c13028c42f0acdf.jpg@80w_80h_90q_1e_1c_1x.jpg", title: "夏天用这款姨妈巾            ,让你体验真正的清爽", user: "迷人的小妖精迷人的小妖精", like: "953", }, { img: "https://www.yuucn.com/wp-content/uploads/2023/04/1682160527-c13028c42f0acdf.jpeg?w=1080&h=1080&x-oss-process=image/resize,w_1080", avatar: "https://www.yuucn.com/wp-content/uploads/2023/04/1682160502-c13028c42f0acdf.jpg@80w_80h_90q_1e_1c_1x.jpg", title: "贵妇级好用的水乳有哪些呢?千万不要去乱尝试贵妇级好用的水乳有哪些呢?千万不要去乱尝试贵妇级好用的水乳有哪些呢?千万不要去乱尝试", user: "迷人的小妖精迷人的小妖精", like: "953", }, { img: "https://www.yuucn.com/wp-content/uploads/2023/04/1682160534-c13028c42f0acdf.jpg?w=1210&h=1210&x-oss-process=image/resize,w_1080", avatar: "https://www.yuucn.com/wp-content/uploads/2023/04/1682160502-c13028c42f0acdf.jpg@80w_80h_90q_1e_1c_1x.jpg", title: "贵妇级好用的水乳有哪些呢?千万不要去乱尝试贵妇级好用的水乳有哪些呢?千万不要去乱尝试贵妇级好用的水乳有哪些呢?千万不要去乱尝试", user: "迷人的小妖精迷人的小妖精", like: "953", }, { img: "https://www.yuucn.com/wp-content/uploads/2023/04/1682160540-c13028c42f0acdf.jpg?w=1000&h=1200&x-oss-process=image/resize,w_1080", avatar: "https://www.yuucn.com/wp-content/uploads/2023/04/1682160502-c13028c42f0acdf.jpg@80w_80h_90q_1e_1c_1x.jpg", title: "夏天用这款姨妈巾                 ,让你体验真正的清爽", user: "迷人的小妖精迷人的小妖精", like: "953", }, { img: "https://www.yuucn.com/wp-content/uploads/2023/04/1682160546-c13028c42f0acdf.jpg?w=828&h=620&x-oss-process=image/resize,w_1080", avatar: "https://www.yuucn.com/wp-content/uploads/2023/04/1682160502-c13028c42f0acdf.jpg@80w_80h_90q_1e_1c_1x.jpg", title: "150元搞定全套护肤品      ,这些护肤好物必须交出来!", user: "迷人的小妖精迷人的小妖精", like: "953", }, ], col: 2, }; }, computed: { itemWidth() { return 138 * 0.5 * (document.documentElement.clientWidth / 375); //rem布局 计算宽度 }, gutterWidth() { return 9 * 0.5 * (document.documentElement.clientWidth / 375); //rem布局 计算x轴方向margin(y轴方向的margin自定义在css中即可) }, }, methods: { scroll(scrollData) { console.log(scrollData); }, switchCol(col) { this.col = col; console.log(this.col); } }, }; </script> <style> * { margin: 0; } .cell-item { width: 100%; margin-bottom: 10px; background: #ffffff; border: 2px solid #f0f0f0; border-radius: 12px 12px 12px 12px; overflow: hidden; box-sizing: border-box; } img { width: 100%; height: auto; display: block; } </style>
声明:本站所有文章      ,如无特殊说明或标注                 ,均为本站原创发布            。任何个人或组织           ,在未征得本站同意时      ,禁止复制、盗用                 、采集                  、发布本站内容到任何网站、书籍等各类媒体平台                 。如若本站内容侵犯了原著者的合法权益                  ,可联系我们进行处理      。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
看电影任务栏为什么不消失(WIN10全屏看电影时,为什么任务栏不能隐藏?) 松滋市论坛(松滋资讯)