瀑布流布局的实现思路

535 阅读2分钟

瀑布流(Waterfall Flow)是一种常见的网页布局方式,它以类似瀑布的形式将内容块依次排列,通常用于展示图片或其他具有不同高度的元素。

瀑布流布局的特点是每个内容块的宽度固定,高度不确定,并且会根据内容块的实际高度进行自动排列。这种排列方式可以让页面呈现出动态和自适应的效果,适用于展示大量不同尺寸的元素。

实现瀑布流布局通常需要通过 CSS 和 JavaScript 来实现。以下是基本的步骤:

  1. HTML 结构:使用 <div> 或其他适当的标签包装每个内容块,并为它们添加类名或其他标识符。
  2. CSS 样式:设置内容块的宽度,并使其浮动(float)或使用网格布局(grid)等方式进行排列。同时也可以设置内容块的边距、间距等样式。
  3. JavaScript:计算每个内容块的位置,并根据每个块的高度动态调整它们的位置。一种常见的做法是使用 JavaScript 库(如 Masonry.js)或自定义脚本来实现布局算法。

瀑布流布局的优点是在有限的空间内能够展示更多的内容,并且适应不同屏幕尺寸。它常被应用于图片展示、新闻资讯、社交网络等需要展示多个不同尺寸元素的场景。

需要注意的是,由于瀑布流布局的特性,当网页中的内容块高度不均匀且动态改变时,可能会导致布局重新计算和重新排列,这可能会影响性能。因此,在实现瀑布流布局时需要优化相关的计算和排列算法,以提高性能和用户体验。

纯代码

<template>
  <div  class="page-main" @touchmove="move">
    <div class="card">
        <div class="coloum1 left"  >
          <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 right">
           <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>


  </div>
</template>


<script setup>
import {ref,onMounted, reactive,nextTick} from 'vue'
const cardList = reactive([ // 测试数据
  {
    num: '0',
    color:  '#FCCF0A',
    height: '100px',

  },
  {
    num: '1',
    color:  '#FCCF0A',
    height: '120px',

  }, 
  {
    num: '2',
    color:  '#FCCF0A',
    height: '130px',

  }, 
  {
    num: '3',
    color:  '#FCCF0A',
    height: '110px',

  }, 
  {
    num: '4',
    color:  '#FCCF0A',
    height: '101px',

  }, 
  {
    num: '5',
    color:  '#FCCF0A',
    height: '180px',

  }, 
  {
    num: '6',
    color:  '#FCCF0A',
    height: '130px',

  }, 
  {
    num: '7',
    color:  '#FCCF0A',
    height: '120px',

  }, 
  {
    num: '8',
    color:  '#FCCF0A',
    height: '150px',

  }, 
  {
    num: '9',
    color:  '#FCCF0A',
    height: '150px',

  }, 
  {
    num: '10',
    color:  '#FCCF0A',
    height: '200px',

  },
 
])

const cardLis = reactive([ // 测试数据
  {
    num: '11',
    color:  '#FCCF0A',
    height: '100px',

  },
  {
    num: '12',
    color:  '#FCCF0A',
    height: '120px',

  }, 
  {
    num: '13',
    color:  '#FCCF0A',
    height: '130px',

  }, 
  {
    num: '14',
    color:  '#FCCF0A',
    height: '110px',

  }, 
  {
    num: '15',
    color:  '#FCCF0A',
    height: '101px',

  }, 
  {
    num: '16',
    color:  '#FCCF0A',
    height: '180px',

  }, 
  {
    num: '17',
    color:  '#FCCF0A',
    height: '130px',

  }, 
  {
    num: '18',
    color:  '#FCCF0A',
    height: '120px',

  }, 
  {
    num: '19',
    color:  '#FCCF0A',
    height: '150px',

  }, 
  {
    num: '20',
    color:  '#FCCF0A',
    height: '150px',

  }, 
  {
    num: '21',
    color:  '#FCCF0A',
    height: '200px',

  },
 
])

const isVisibility = ref(true)
// 由于渲染时候对数据的两次赋值,则会出现一次闪现,需要防抖
onMounted(()=>{
  equallyCard(cardList)
  nextTick(()=>{
    caLFlex(cardList)
  }).then(()=>{
    isVisibility.value = true
  })
})



const cardList1 = ref([]) // 各列的数据
const cardList2 = ref([])


function equallyCard(val){
  // 平分数据,确保页面上遍历卡片dom的真实顺序与平分的一致 document.querySelectorAll('.card-item'))
  let num = parseInt(val.length/2)
  val.forEach((item,index)=>{
    //加入数据有12条前6条归数组1后6条归数组2
    if(index<num){
       cardList1.value.push(item)
       return
    }
    if(index<2*num){
       cardList2.value.push(item)
        return
    }
    
  })
}

let arr1 = ref([])
let arr2 = ref([])

function caLFlex(val){
  

  let heightArry_1 = [] // 第一列的卡片高度
  let heightArry_2 = [] // 第二列的卡片高度

  Array.from(document.querySelectorAll('.card-item')).forEach((item,index) =>{
      if(index === 0){ // 第一行中的元素无需判断,直接加到新的数组中
          heightArry_1.push(item.offsetHeight)
          arr1.value.push(val[index])
          return
      }
      if(index === 1){
          heightArry_2.push(item.offsetHeight)
           arr2.value.push(val[index])
           return
      }
     //三元表达式 数组的长度是否为true(0位假值)  如果是 使用reduce方法进行累加,如果不是就为0
   //判断当前数组长度是否0,如果不是就使用reduce计算累加值,如果为0,高度就为0
     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 // 第二列的总高
    
     // 找到最小值
     let minNumber = Math.min(heightTotal_1,heightTotal_2)
     switch (minNumber) {
       case heightTotal_1:
          heightArry_1.push(item.offsetHeight)
          arr1.value.push(val[index])
         break
      case heightTotal_2:
         heightArry_2.push(item.offsetHeight)
          arr2.value.push(val[index])
         break
     }
  })

// 重新将数据赋值给各列数组
   
   
  cardList1.value = arr1.value
  cardList2.value = arr2.value
}



function move(){
  
  let page = Math.abs(document.querySelector('.page-main').scrollTop) //盒子的超出滚动距离
  let boxheight = document.querySelector('.page-main').clientHeight//盒子的高
 let height = document.querySelector('.coloum1').scrollHeight//列表1的滚动高度
 let height2 = document.querySelector('.coloum2').scrollHeight//列表2的滚动高度

 let minheight = Math.min(height,height2) //取最列表1与列表2高度的小值
//如果滚动高度+盒子高大于最小的列表高时相当于相对矮的列表触底
 if(page+boxheight>minheight){
  equallyCard(cardLis)

    caLFlex(cardLis)
 }
}
</script>

<style lang="scss" scoped>
.page-main{
    background: #ffffff;
    height:400px;
    overflow: scroll;
    padding: 0 30px;
    .card{
      
        .card-item{
          visibility: hidden;
          margin-bottom: 20px;
          text-align: center;
          width: 200px;
          margin-right: 10px;
          border-radius: 16px;
        }
        .visibles {
          visibility: visible;
        }
    }
}

.left{
  float: left;
}
.right{
  float: right;
}
</style>