瀑布流的多种实践

瀑布流的多种实践

可实现方案:flex弹性布局;column多行布局;js计算的两种方案

1.flex弹性布局;

将外层父元素设置display:横向布局 ,再设置 flex-flow:column wrap 纵向布局且换行即可

 .container {
    width: 100%;
    height: 70vh;
    display: flex;
    flex-flow: column wrap;

    .item {
      width: calc((100% - 60px) / 4);
      height: 10vh;
      background: rgba(12, 50, 97, 0.8);
      border: 1px solid #9cdeff;
      color: #fff;
      margin: 10px;
      display: flex;
      justify-content: center;
      align-items: center;

      &:nth-of-type(2n),
      &:nth-of-type(5),
      &:nth-of-type(11) {
        height: 15vh;
      }
    }

 

2.column多行布局;

column 实现瀑布流主要依赖两个属性。 一个是 column-count 属性,是分为多少列。 一个是 column-gap 属性,是设置列与列之间的距离。

注意防止断点,否则dom会显示不全。

  .container {
    width: 100%;
    column-count: 5
    column-gap: 10px;

    .item {
      height: 10vh;
      background: rgba(12, 50, 97, 0.8);
      border: 1px solid #9cdeff;
      color: #fff;
      display: flex;
      justify-content: center;
      align-items: center;
      margin-bottom: 10px;

      break-inside: avoid; /*防止断点*/

      &:nth-of-type(2n),
      &:nth-of-type(5),
      &:nth-of-type(11) {
        height: 15vh;
      }
    }
  }
}

未设置:break-inside: avoid:

设置break-inside: avoid后:

对于将文字分列展示挺合适:

3.js计算

无论是flex还是column,用css写瀑布流,每一块都是从上往下排列,不能做到从左到右排列,并且不会识别哪一块图片放在哪个地方合适,对于需要动态添加数据的情况效果并不好。

我们可以通过用JS 计算来解决,我们先来看一种方案:

思路分析

  1. 瀑布流布局的特点是等宽不等高。

  2. 为了让最后一行的差距最小,从第二行开始,需要将图片放在第一行最矮的图片下面,以此类推。

  3. 父元素设置为相对定位,图片所在元素设置为绝对定位。然后通过设置 top 值和 left 值定位每个元素。

<script type="text/javascript">
 2     // 定义瀑布流算法函数
 3     function fall() {
 4         const minGap = 20; // 最小间距,让每一列的最小空隙可以自定义,避免太过拥挤的情况发生。但是,会通过计算得到真实的间距。
 5         const itemWidth = 300; // 每一项的宽度,即当前每一个图片容器的宽度。保证每一列都是等宽不等高的。
 6         const scrollBarWidth = getScrollbarWidth();    // 获取滚动条的宽度
 7         const pageWidth = window.innerWidth - scrollBarWidth; // 获取当前页面的宽度 = window.innerWidth - 滚动条的宽度
 8         const column = Math.floor(pageWidth / (itemWidth + minGap)); // 实际列数=页面宽度/(图片宽度+最小间距)
 9         const gap = (pageWidth - itemWidth * column) / column/2; // 计算真实间距 = (页面宽度- 图片宽度*实际列数)/实际列数/2
10         const items = document.querySelectorAll('img'); // 获取所有的外层元素
11         const heightArr = []; // 定义一个空数组,保存最低高度。
12         
13         // 获取滚动条的宽度
14         function getScrollbarWidth() {
15             const oDiv = document.createElement('div');//创建一个div
16             // 给div设置样式。随便定义宽高,只要能获取到滚动条就可以
17             oDiv.style.cssText = `width: 50px;height: 50px;overflow: scroll;` 
18             document.body.appendChild(oDiv);//把div添加到body中
19             const scrollbarWidth = oDiv.offsetWidth - oDiv.clientWidth;// 使最大宽度和可视宽度相减,获得到滚动条宽度。
20             oDiv.remove();//移除创建的div
21             return scrollbarWidth;//返回滚动条宽度
22         }
23         
24         
25         for (let i = 0; i < items.length; i++) {
26             // 遍历所有的外层容器
27             const height = items[i].offsetHeight;
28             // 如果当前处在第一行
29             if (i < column) {
30                 // 直接设置元素距离上部的位置和距离左边的距离。
31                 items[i].style.cssText = `top: ${gap}px;left: ${(itemWidth + gap) * i + gap}px`;
32                 // 保存当前元素的高度。
33                 heightArr.push(height);
34             } else {
35                 // 不是第一行的话,就进行比对。
36                 let minHeight = heightArr[0]; // 先保存第一项的高度
37                 let minIndex = 0; // 保存第一项的索引值
38                 for (let j = 0; j < heightArr.length; j++) {
39                     // 通过循环遍历比对,拿到最小值和最小值的索引。
40                     if (minHeight > heightArr[j]) {
41                         minHeight = heightArr[j];
42                         minIndex = j;
43                     }
44                 }
45                 // 通过最小值为当前元素设置top值,通过索引为当前元素设置left值。
46                 items[i].style.cssText = `top: ${minHeight + gap *2}px; left: ${(itemWidth + gap) * minIndex + gap}px`;
47                 // 并修改当前索引的高度为当前元素的高度
48                 heightArr[minIndex] = minHeight + gap + height;
49             }
50         }
51     }
52     // 页面加载完成调用一次。
53     window.onload = fall;
54     // 页面尺寸发生改变再次调用。
55     window.onresize = fall;
56 </script>

上面这种方案HTML结构较简单 适合绝大多数情况。

但对于需要横向排序号的dom,定位后无法正确排序,对于这种业务需求,可以采用另一种方案:

思路分析

  1. 瀑布流布局的特点是等宽不等高。

  2. 需要多少列就建立多少个dataList 。从第二行开始,每次将数据push到高度最短的列里面,以此类推。

  3. 每项的序号为 m*idx+n ( m为总列数 n为该项位于的列数)

     

     

    具体实现

    1.页面布局结构代码  

     <div class="box">
        <div class="col" ref="col1">
          <transition-group name="list">
            <div class="item" v-for="item in dataList1" :key="item.id">{{ 4 * imgIdx + 1 }}</div>
          </transition-group>
        </div>
        <div class="col" ref="col2">
          <transition-group name="list">
            <div class="item" v-for="item in dataList2" :key="item.id">{{ 4 * imgIdx + 2 }}</div>
          </transition-group>
        </div>
        <div class="col" ref="col3">
          <transition-group name="list">
            <div class="item" v-for="item in dataList3" :key="item.id">{{ 4 * imgIdx + 3 }}</div>
          </transition-group>
        </div>
        <div class="col" ref="col4">
          <transition-group name="list">
            <div class="item" v-for="item in dataList4" :key="item.id">{{ 4 * imgIdx + 4 }}</div>
          </transition-group>
        </div>
      </div>        

    2.js代码

mountMenu(arg) {
      if (this.mainMenuList.length <= 4) {
          //数据不超过一行时 特殊处理
        if (this.mainMenuList[0]) {
          this.dataList1.push(this.mainMenuList[0])
        }
        if (this.mainMenuList[1]) {
          this.dataList2.push(this.mainMenuList[1])
        }
        if (this.mainMenuList[2]) {
          this.dataList3.push(this.mainMenuList[2])
        }
        if (this.mainMenuList[3]) {
          this.dataList4.push(this.mainMenuList[3])
        }
        return
      }
      var temp = this.mainMenuList
      var index = arg || 0
      var refName = this.selectCol()
      if (temp.length > index) {
       //把数据push到前高度最小的列对应的dataList中
        this[refName].push(this.mainMenuList[index])
        this.$nextTick(() => {
       //等待当前DOM渲染完成后在继续执行,否则在获取最小高度的列时,每次获取到的高度都为0
          this.mountMenu(index + 1)
        })
      }
    },
    selectCol() {
     //获取当前高度最小的列
      var getHeight = (ref) => {
        return this.$refs[ref].offsetHeight
      }
      var height1 = getHeight('col1')
      var height2 = getHeight('col2')
      var height3 = getHeight('col3')
      var height4 = getHeight('col4')
      switch (Math.min(height1, height2, height3, height4)) {
        case height1:
          return 'dataList1'
          break
        case height2:
          return 'dataList2'
          break
        case height3:
          return 'dataList3'
        case height4:
          return 'dataList4'
          break
      }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值