zoukankan      html  css  js  c++  java
  • 浅析瀑布流布局原理及实现方式

    一、瀑布流

      瀑布流布局有一个专业的英文名称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
          }
        },
      }
    }

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

  • 相关阅读:
    ThinkPHP3.1快速入门(12)自动验证
    ThinkPHP CURD方法盘点:page方法
    ThinkPHP CURD方法盘点:table方法
    ThinkPHP CURD方法盘点:order方法
    ThinkPHP CURD方法盘点:data方法
    openssl安装问题导致nginx添加ssl模块失败
    rsync介绍
    基于CRF序列标注的中文依存句法分析器的Java实现
    千万级巨型汉语词库分享
    几个开源分词系统所使用标注集的来源
  • 原文地址:https://www.cnblogs.com/goloving/p/14882706.html
Copyright © 2011-2022 走看看