浅析瀑布流布局原理及实现方式

一、瀑布流

  瀑布流布局有一个专业的英文名称Masonry Layouts。瀑布流布局已经有好多年的历史了,我最早知道这个名词的时候大约是在2012年,当时Pinterest网站的布局就是使用的这种流式布局,简言之像Pinterest网站这样的布局就称之为瀑布流布局,也有人称之为Pinterest 布局。

  瀑布流又称瀑布流式布局,是比较流行的一种网站页面布局方式。即多行等宽元素排列,后面的元素依次添加到其后,等宽不等高,根据图片原比例缩放直至宽度达到我们的要求,依次按照规则放入指定位置。

  瀑布流布局的核心是基于一个网格的布局,而且每行包含的项目列表高度是随机的(随着自己内容动态变化高度),同时每个项目列表呈堆栈形式排列,最为关键的是,堆栈之间彼此之间没有多余的间距差存大。还是上张图来看看我们说的瀑布流布局是什么样子。

1、为什么使用瀑布流

  瀑布流布局在我们现在的前端页面中经常会用的到,它可以有效的降低页面的复杂度,节省很多的空间,对于整个页面不需要太多的操作,只需要下拉就可以浏览用户需要看到的数据;并且,在当前这个APP至上的时代,瀑布流可以提供很好的用户体验,通过结合下拉刷新,上拉加载进行数据的懒加载等操作,对于用户的体验感来说是接近于满分的!

2、瀑布流的特点

  其实瀑布流的特点就是参差不齐的排列方式,以及流式布局的扩展性,可以通过界面展示给用户多条数据,并且让用户可以有向下浏览的冲动。

二、实现方式

1、纯 css 瀑布流:( multi-columns 方法 )

  首先最早尝试使用纯CSS方法解决瀑布流布局的是CSS3 的Multi-columns

  其最早只是用来用来实现文本多列排列(类似报纸杂志样的文本排列)。但对于前端同学来说,他们都是非常具有创意和创新的,有人尝试通过Multi-columns相关的属性column-countcolumn-gap配合break-inside来实现瀑布流布局。

/* 这里是第一次接触到 column-columns 这个属性,这是一个可以设置将div元素中的文本分成几列 */
/* 默认值是:auto */

column-count:3;
-moz-column-count:3; /* Firefox */
-webkit-column-count:3; /* Safari and Chrome */

/* 注意:IE9及更早 IE 版本浏览器不支持 column-count 属性 */
/* 这里还会用到另一个属性 column-gap,用来调整边距,实现瀑布流布局 */
    .demo-1{
       -moz-column-count:3; /* Firefox */
       -webkit-column-count:3; /* Safari 和 Chrome */
       column-count:3;
       -moz-column-gap: 1em;
       -webkit-column-gap: 1em;
       column-gap: 1em;
       width: 80%;
       margin:0 auto;
    }
    .item {
        padding: 2em;
        margin-bottom: 2em;
        -webkit-column-break-inside: avoid;
        break-inside: avoid; /*防止断点*/
        background: #ccc;
        text-align: center;
    }

  column-countcolumn-gap,前者用来设置列数,后者设置列间距。

  上面控制了列与列之间的效果,但这并不是最关键之处。当初纯CSS实现瀑布流布局中最关键的是堆栈之间的间距,而并非列与列之间的控制(说句实话,列与列之间的控制float之类的就能很好的实现)。找到实现痛楚,那就好办了。或许你会问有什么CSS方法可以解决这个。在CSS中有一个break-inside属性,这个属性也是实现瀑布流布局最关键的属性。

  其中break-inside:avoid为了控制文本块分解成单独的列,以免项目列表的内容跨列,破坏整体的布局。当然为了布局具有响应式效果,可以借助媒体查询属性,在不同的条件下使用column-count设置不同的列。

  但是这里还是有个弊端,这并不符合瀑布流的原理,如果使用纯css写瀑布流,则每一块都是从上往下排列,不能做到从左到右排列,最主要的是不会识别哪一块图片放在哪个地方合适,若是再配合动态加载,效果会特别不好,所以只能通过JS来实现瀑布流。

2、瀑布流的位置分析图解

  那么这里用图片来分析一下我们想要的瀑布流是什么样的。

  如下方图片。假设一排放5张图片。当第一排排满足够多的等宽图片时,显示的是这样的。那么假如我们要放第6张图片的时候,应该放在什么位置呢?

  如果按照我们的正常逻辑来想,应该是放在第一张图片下面,依次水平排列过去(如下图)

  但现实并非如此!在瀑布流中,从第2行开始,接下去的每一张图片都会放在上行中高度最低的那一列图片下方。(如下图)

  为什么呢?因为放置它之前,这一列的高度为所有列中最小,所以会放置在这个地方。

  那么如果再继续放置下去,第七张图片应该放在第三列图片下方,以此类推。

  所以每次加载图片时,会需要判断哪一列的图片累计的高度最小,那么下一张图片就放在哪一列,即瀑布流算法去判断图片的确定位置。

3、JS实现

  结构示意图:

  可扩展要素:(1)外层容器的box高度不固定;(2)每列col的宽度可自定义;(3)列数可自定义,取决于有几个数据dataList,每个列的数据对应一个 dataList。

(1)页面布局结构代码

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

(2)加载数据

  当组件mounted时获取数据,获取到数据后执行mountMenu()方法,mountMenu()方法将会通过selectCol()选择当前高度最小的列,并把数据push到对应的dataList中,mountMenu()会在每次执行时递归调用,直到遍历完所有的数据。
export default {
  data() {
    return {
      mainMenuList: [],
      dataList1: [],
      dataList2: [],
      dataList3: [],
      dataList4: [],
    }
  },
  mounted() {this.mountMenu()
  },
  methods: {
    mountMenu(arg) {
      var temp = this.mainMenuList
      var index = arg || 0
      var refName = this.selectCol()
      if (temp.length > index) {
        this[refName].push(this.mainMenuList[index])
        this.$nextTick(() => {
          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
      }
    },
  }
}

  这样就可以实现一个我们想要的瀑布流了,但是不好的是频繁的取高度,会导致频繁的回流。所以如果不是一定要使用的话,就尽量避免吧。

posted @ 2021-06-14 16:37  古兰精  阅读(4050)  评论(0编辑  收藏  举报