1. 程式人生 > 其它 >淺析瀑布流佈局原理及實現方式

淺析瀑布流佈局原理及實現方式

一、瀑布流

  瀑布流佈局有一個專業的英文名稱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
      }
    },
  }
}