淺析瀑布流佈局原理及實現方式
一、瀑布流
瀑布流佈局有一個專業的英文名稱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
}
},
}
}