如何实现瀑布流布局?

993 阅读1分钟

在小编刚入行的时候,参考着度娘里的答案,用js实现过一次瀑布流布局。所以在接到瀑布流布局的设计稿时,第一反应就是利用js实现。

js实现

大概思路是:

css

    1. 瀑布流容器设置绝对定位,然后给所有子元素设置相对定位。
    1. 通过ajax请求拿到数据,并渲染到页面上
	div class="waterfall-list-container" >
      <div class="item-container" >
      </div>
    </div>
    css
    .waterfall-list-container {
      position: relative;
      .item-container {
        position: absolute;
      }
   }

js

    1. 定义一个数组变量(数组长度和瀑布流布局所展示的列数相等),用于记录每一列的高度变化。
    1. 获取到所有瀑布流容器里的子元素,并遍历。
    1. 根据记录高度变化数组中的值,依次给每一列的子元素设置距离顶部的高度。并改变瀑布流容器的高度。
      // 瀑布流容器
      const parent = document.querySelector('.waterfall-list-container')
      parent.style.height = `${0}px`
      // 瀑布流当中的item
      const waterfalls = document.querySelectorAll('.item-container')
      if (!waterfalls.length) {
        return
      }
      // 列数
      const col = 2
      // 间隔
      const xspace = this.gutterWidth
      const yspace = this.gutterWidth
      const w = waterfalls[0].offsetWidth
      // 一个数组,记录每一列距离顶部高度变化
      const colHs = []
      waterfalls.forEach((ele, index) => {
        // 瀑布流容器高度
        const parentH = parent.offsetHeight
        // 对应的上一个元素的高度
        const preDomH = index - col >=0 && waterfalls[index - col].offsetHeight
        // 当前元素高度
        const currentDomH = ele.offsetHeight
        if (index < col) {
          // 第一行比较特殊,距离顶部的距离都是0
          colHs[index] = 0
        } else {
          // 记录每一列高度的变化
          const i = index % col
          preDomH && (colHs[i] += preDomH + yspace)
        }
        ele.style.top = `${colHs[index % col]}px`
        ele.style.left = `${(index % col) * (w + xspace)}px`
        let maxH = colHs[0]
        colHs.forEach(h => {
          +maxH < h && (maxH = h)
        })
        if (maxH) {
          parent.style.height = `${+maxH + currentDomH}px`
        } else {
          parentH < currentDomH && (parent.style.height = `${currentDomH}px`)
        }
      })

自从用了这个方法之后,测试提给我的bug就没断过o(╥﹏╥)o,大致就是有些子元素重叠的问题。我猜测的原因是:因为小编所做项目的子元素中是带图的,所有应该是js执行的时候图片还未加载完成,导致高度计算错误。(如果不对,欢迎各位大佬指正!)

我思前想后,这不行啊。而且vue项目里面有操作dom元素的操作,也是糟糕了点。于是再次拜访了万能的度娘,找到了纯css方法实现了瀑布流的方法。

纯css实现

纯css实现瀑布流

按照百度到的方法试了一下,果真方便快捷有效。就是:纯css布局出来的瀑布流是纵向排序,但是需求上要求是横向布局(我的天,和需求不符啊~ ~ ~ ~)

demo

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<style>
		.masonry {
		    column-count: 2;
		    column-gap: 0;
		}
		.item {
		    break-inside: avoid;
		    box-sizing: border-box;
			text-align: center;
			line-height: 150px;
			border: 1px solid skyblue;
		}
	</style>
	<body>
		<div class="masonry">
		    <div class="item i1">
		        <div class="item__content">
					1
		        </div>
		    </div>
		    <div class="item i2">
		        <div class="item__content">
					2
		        </div>
		    </div>
			<div class="item i3">
			    <div class="item__content">
			    </div>
			</div>
			<div class="item i4">
				3
			    <div class="item__content">
			    </div>
			</div>
			<div class="item i5">
				4
			    <div class="item__content">
			    </div>
			</div>
			<div class="item i6">
				5
			    <div class="item__content">
			    </div>
			</div>
			<div class="item i7">
				6
			    <div class="item__content">
			    </div>
			</div>
			<div class="item i8">
				7
			    <div class="item__content">
			    </div>
			</div>
			<div class="item i9">
				8
			    <div class="item__content">
			    </div>
			</div>
			<div class="item i10">
				9
			    <div class="item__content">
			    </div>
			</div>
		    <!-- more items -->
		</div>
	</body>
</html>

效果图如下:

哎!这可如何是好呢?看着设计图都快愁秃了o(╥﹏╥)o,正愁着小编突然灵光乍现,瀑布流布局可以先分成想要的列数,然后设置列之间左右间距以及每一列中的子元素的上下间距。至于怎么分成不同的列,数据顺序还是从左到右的呢?前面也说了,小编的项目是用的vue框架,利用 % 运算很快就可以实现效果了。

其他方法

    1. 瀑布流父组件声明flex布局
    1. 渲染相应列数的子元素
    1. 渲染每一列的子元素 区分相同列方法(列表下标 % 列数 === 当前渲染列数下标)
  <div class="container">
  	<!-- col:瀑布流的列数 -->
    <div class="listl-container" v-for="(item, index) in col" :key="colIndex">
      <div
        v-for="(items, indexs) in list"
        v-if="indexs % col === index"
        :key="`wf-list-${index}`"
        class="item">
        <!-- you content -->
      </div>
    </div>
  </div>
  
  .container {
    display: flex;
    .list-container {
      flex: 1;
      margin: 0 10px;
      &:first-of-type {
        margin-right: 4px;
      }
      &:last-of-type {
        margin-left: 4px;
      }
    }
  }

其实这个方法也不是最佳的,要渲染col * list.length 次。然而目前还没有想出其他更好的方法,只能先用着这个了~