当前位置:首页 » 《随便一记》 » 正文

实现瀑布流布局的四种方法

12 人参与  2023年05月02日 17:45  分类 : 《随便一记》  评论

点击全文阅读


文章目录

一、 什么是瀑布流布局1.是什么2.特点 二、有什么优缺点1.优点2.缺点 三、实现方法法一、纯css的写法:【multi-column 多栏布局】1.两个重要属性2.特点3.缺点4.注意点:5.实现的代码模式(以下用vue3.0来书写) 法二、泳道的思想:【用flex弹性布局+计算元素高度实现布局】1.原理的分析2.算法思路:通过上面的分析则能了解瀑布流的思路了3.代码实现思路4.代码实现(以下用vue3.0来实现) 法三、绝对定位实现:【精确计算每个子元素绝对定位到瀑布流它应该去的地方,需要后期一些优化,并不推荐使用】1.缺点2.实现的原理3.代码实现思路4.代码实现(vue3.0来实现) 法四、插件实现:【用插件vue-waterfall2 不是很建议使用,一般用于图片的加载,但是复杂的业务就不是很ok了】1.安装依赖2.引入3.相关api4.代码实现

一、 什么是瀑布流布局

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://image.watsons.com.cn//upload/8a316140.png?w=377&h=451&x-oss-process=image/resize,w_1080",                    avatar:                        "https://img.xiaohongshu.com/avatar/5b7d198a7e6e15000155f7c9.jpg@80w_80h_90q_1e_1c_1x.jpg",                    title: "最近浴室新宠,日系神仙好物了解一下~",                    user: "迷人的小妖精迷人的小妖精",                    like: "953",                },                {                    img:                        "https://image.watsons.com.cn//upload/5c3e51e4.jpg?w=720&h=960&x-oss-process=image/resize,w_1080",                    avatar:                        "https://img.xiaohongshu.com/avatar/5b7d198a7e6e15000155f7c9.jpg@80w_80h_90q_1e_1c_1x.jpg",                    title: "真的是万能.超级实用.包包必备单品! ! !",                    user: "迷人的小妖精迷人的小妖精",                    like: "952",                },                {                    img:                        "https://image.watsons.com.cn//upload/bef41e67.JPG?w=712&h=534&x-oss-process=image/resize,w_1080",                    avatar:                        "https://img.xiaohongshu.com/avatar/5b7d198a7e6e15000155f7c9.jpg@80w_80h_90q_1e_1c_1x.jpg",                    title: "150元搞定全套护肤品,这些护肤好物必须交出来!",                    user: "迷人的小妖精迷人的小妖精",                    like: "953",                },                {                    img:                        "https://image.watsons.com.cn//upload/13afe9a7.jpg?x-oss-process=image/resize,w_1080",                    avatar:                        "https://img.xiaohongshu.com/avatar/5b7d198a7e6e15000155f7c9.jpg@80w_80h_90q_1e_1c_1x.jpg",                    title: "夏天用这款姨妈巾,让你体验真正的清爽",                    user: "迷人的小妖精迷人的小妖精",                    like: "953",                },                {                    img:                        "https://image.watsons.com.cn//upload/642cb83c.jpeg?w=1080&h=1080&x-oss-process=image/resize,w_1080",                    avatar:                        "https://img.xiaohongshu.com/avatar/5b7d198a7e6e15000155f7c9.jpg@80w_80h_90q_1e_1c_1x.jpg",                    title:                        "贵妇级好用的水乳有哪些呢?千万不要去乱尝试贵妇级好用的水乳有哪些呢?千万不要去乱尝试贵妇级好用的水乳有哪些呢?千万不要去乱尝试",                    user: "迷人的小妖精迷人的小妖精",                    like: "953",                },                {                    img:                        "https://image.watsons.com.cn//upload/98c7c4c3.jpg?w=1210&h=1210&x-oss-process=image/resize,w_1080",                    avatar:                        "https://img.xiaohongshu.com/avatar/5b7d198a7e6e15000155f7c9.jpg@80w_80h_90q_1e_1c_1x.jpg",                    title:                        "贵妇级好用的水乳有哪些呢?千万不要去乱尝试贵妇级好用的水乳有哪些呢?千万不要去乱尝试贵妇级好用的水乳有哪些呢?千万不要去乱尝试",                    user: "迷人的小妖精迷人的小妖精",                    like: "953",                },                {                    img:                        "https://image.watsons.com.cn//upload/25ab20fe.JPG?w=1000&h=1200&x-oss-process=image/resize,w_1080",                    avatar:                        "https://img.xiaohongshu.com/avatar/5b7d198a7e6e15000155f7c9.jpg@80w_80h_90q_1e_1c_1x.jpg",                    title: "夏天用这款姨妈巾,让你体验真正的清爽",                    user: "迷人的小妖精迷人的小妖精",                    like: "953",                },                {                    img:                        "https://image.watsons.com.cn//upload/083767f0.JPG?w=828&h=620&x-oss-process=image/resize,w_1080",                    avatar:                        "https://img.xiaohongshu.com/avatar/5b7d198a7e6e15000155f7c9.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>

点击全文阅读


本文链接:http://zhangshiyu.com/post/60267.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1