一、瀑布流
瀑布流布局有一個專業的英文名稱Masonry Layouts。瀑布流布局已經有好多年的歷史了,我最早知道這個名詞的時候大約是在2012年,當時Pinterest網站的布局就是使用的這種流式布局,簡言之像Pinterest網站這樣的布局就稱之為瀑布流布局,也有人稱之為Pinterest 布局。
瀑布流又稱瀑布流式布局,是比較流行的一種網站頁面布局方式。即多行等寬元素排列,后面的元素依次添加到其后,等寬不等高,根據圖片原比例縮放直至寬度達到我們的要求,依次按照規則放入指定位置。
瀑布流布局的核心是基於一個網格的布局,而且每行包含的項目列表高度是隨機的(隨着自己內容動態變化高度),同時每個項目列表呈堆棧形式排列,最為關鍵的是,堆棧之間彼此之間沒有多余的間距差存大。還是上張圖來看看我們說的瀑布流布局是什么樣子。

1、為什么使用瀑布流
瀑布流布局在我們現在的前端頁面中經常會用的到,它可以有效的降低頁面的復雜度,節省很多的空間,對於整個頁面不需要太多的操作,只需要下拉就可以瀏覽用戶需要看到的數據;並且,在當前這個APP至上的時代,瀑布流可以提供很好的用戶體驗,通過結合下拉刷新,上拉加載進行數據的懶加載等操作,對於用戶的體驗感來說是接近於滿分的!
2、瀑布流的特點
其實瀑布流的特點就是參差不齊的排列方式,以及流式布局的擴展性,可以通過界面展示給用戶多條數據,並且讓用戶可以有向下瀏覽的沖動。
二、實現方式
1、純 css 瀑布流:( multi-columns 方法 )
首先最早嘗試使用純CSS方法解決瀑布流布局的是CSS3 的Multi-columns。
其最早只是用來用來實現文本多列排列(類似報紙雜志樣的文本排列)。但對於前端同學來說,他們都是非常具有創意和創新的,有人嘗試通過Multi-columns相關的屬性column-count、column-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-count和column-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 } }, } }
這樣就可以實現一個我們想要的瀑布流了,但是不好的是頻繁的取高度,會導致頻繁的回流。所以如果不是一定要使用的話,就盡量避免吧。
