本篇文章帶大家了解一下瀑布流布局,介紹一下三種靠譜JS方案,以及N種不靠譜CSS方案。有一定的參考價值,有需要的朋友可以參考一下,希望對大家有所幫助。
本著實用精神,我們今天來分享一下瀑布流布局(昨天有個小兄弟問我怎么做,我找了半天沒找到,啊原來寫在內網了)。
演示地址: http://www.lilnong.top/static/html/waterfall.html
瀑布流布局是什么?
比如說 花瓣網、蘑菇街 (我下面貼圖了), 這些網站在顯示內容的時候就使用了瀑布流布局。
我們也想做一個展示我們設計稿(定寬,不定高)的頁面,瀑布流是很棒的一種方案。
瀑布流布局其核心是基于一個網格的布局,而且每行包含的項目列表高度是隨機的(隨著自己內容動態變化高度),同時每個項目列表呈堆棧形式排列,最為關鍵的是,堆棧之間彼此之間沒有多余的間距差存大。還是上圖來看看我們說的瀑布流布局是什么樣子。
網站 | 蘑菇街 | 花瓣網 | 京東 | VV |
---|---|---|---|---|
截圖 | ![]() |
![]() |
![]() |
![]() |
方案 | 分通道 | absolute |
grid、inline、float 魔性方案
也算是純 CSS 方案吧,本質上來講是依賴文檔流,從左到右,從上到下。
方案 | grid | inline | float | bootstrap-grid |
---|---|---|---|---|
截圖 | ![]() |
![]() |
![]() |
![]() |
可以看到在文檔流布局中有非常明顯的行的概念,當一個行被撐開就會留下空白,行與行不會重疊。這里最魔性的就是 float 布局了。
DOM 結構
div.list // 設置 gird 或者 block,注意清除浮動 div.item // 設置為 inline 或者 float,使其能流動 img // 設置定寬,高度自適應,間距等。
grid 方案說明
.wrap-waterfall--grid img{vertical-align: top;width: 100px} .wrap-waterfall--grid .list{ display: grid; grid-gap: 10px; /* 可以看到,網格大小,占據位置是需要提前設定的 */ grid-template-columns: repeat(4, 1fr); grid-auto-rows: minmax(50px, auto); }
grid 在某些情況下會比 flex 好用。比如說需要突破行的限制,但是只適用于固定布局,如下圖的布局,如果不使用grid你會如何實現呢?
網傳有 gird 實現瀑布流布局的方案,但是我看了幾個他們不是色塊,就是圖片變形、裁剪,方案是用 nth-child 定高,太恐怖了吧。
columns、flex CSS實現 不靠譜方案
也是純 CSS 方案,相比較上面的方案而言,方案已經可以接受,只是還有部分問題。
- 順序是先垂直,后水平
- (columns)兼容性問題
- (flex)需要給一個固定高度,會出現超出設定列,以及無法充滿設定列。
方案 | columns | flex |
---|---|---|
截圖 | ![]() |
![]() |
columns 方案
天生支持,只需要給父級設置即可 columns: 4; column-gap: 6px;
。
flex 方案
flex-flow: column wrap;height: 2300px;
默認情況下是水平排列,通過修改為垂直排列并且允許換行,之后把通過固定高度使內容換行。
absolute、通道 高度計算方案 靠譜方案
方案 | absolute | 取余分通道 | 計算高度分通道 |
---|---|---|---|
頭部截圖 | ![]() |
![]() |
![]() |
這里的方案就靠譜起來了,可以滿足我們使用要求。
我們來回憶一下我們的需求:展示一些內容,內容有特性定寬,不定高。不定高一般是因為內容長度或者高度不一致導致的,常見內容又分為兩種文字和圖片。
-
文字的話,在沒有異步字體的情況下,可以理解為同步就可以獲取到盒子高度。
-
圖片的話,因為加載是異步的,所以獲取盒子的真實高度也是異步的。但是這里一般分為兩種情況
-
無高度,那么可以通過onload來監聽圖片加載完成。等圖片加載完成再去獲取高度。
- 有高度,這種方案一般用在封面圖、或者文章中,在上傳圖片的時候會保存原圖尺寸,這個時候我們就可以直接使用已有數據。
獲取圖片高度
// 用于獲取圖片的真實高度 naturalHeight: 1180 // 用于獲取圖片的真實寬度 naturalWidth: 1200 //用戶獲取圖片當前的渲染高度(會受 css 影響) height: 98 //用戶獲取圖片當前的渲染寬度(會受 css 影響) width: 100 // 可返回瀏覽器是否已完成對圖像的加載。如果加載完成,則返回 true,否則返回 fasle。 complete 屬性 // 可以監聽到圖片加載完成的動作 onload
基于上面的內容,那我們可以先判斷 complete 屬性,
function getImageSize(img){ if(img.complete){ return Promise.resolve({ naturalHeight: img.naturalHeight, naturalWidth: img.naturalWidth, height: img.height, width: img.width, }) }else{ return new Promise((resolve, reject)=>{ img.addEventListener('load', ()=>{ resolve({ naturalHeight: img.naturalHeight, naturalWidth: img.naturalWidth, height: img.height, width: img.width, }) }) }) } } /* // 測試用例 el = document.createElement('img'); el.src = 'http://cors-www.lilnong.top/favicon.ico?'+Math.random() getImageSize(el).then(console.log).catch(console.error) setTimeout(()=>getImageSize(el).then(console.log).catch(console.error), 1000) */
absolute 計算高度方案
因為普通的布局已經無法滿足我們的需求,所以我們可以考慮通過 position: absolute
來使內容通過絕對定位來顯示。
核心操作就是維護每個元素的 left、top,然后使用 left 和 top 去渲染到正確位置。
getListPosition(){ // 視口寬度 / 每列寬度 得出劃分為幾列 let col = this.screenWidth / this.itemWidth >> 0; var arr = []; for(var i = 0; i < col; i++) arr.push({ list: [], height: 0, }) // 遍歷所有元素 this.listInfo.forEach((item,idx)=>{ // 找到最低的一列 var colIndex = 0; for(var i = 1; i < col; i++){ if(arr[colIndex].height > arr[i].height){ // colItem = arr[i] colIndex = i } } // 修改元素的信息 // 所屬列 item.line = colIndex; // 計算之后的 top 距離 item.top = arr[colIndex].height+ 'px'; // 計算之后的 left 距離 item.left = colIndex * (this.itemWidth + 10) + 'px' // 累加操作 arr[colIndex].list.push(item); arr[colIndex].height += item.height + 10; }) return arr },
通過計算,我們可以到,瀑布流布局下每個元素的位置,通過絕對定位就可以實現。
根據下標,來渲染到不同的通道 idx % 4
因為上個方案用到了絕對定位,那么有沒有不用絕對定位的方案呢?回到我們的問題點上 定寬,不定高,那我們完全可以通過分開渲染放棄 absolute 來實現。
jsGroupList(){ return this.list.reduce((s,n,idx)=>{ // 根據下標,直接分配所屬列 s[idx % 4].push({idx: idx, item: n}) return s }, [[],[],[],[],]) },
看開頭是實現類似的功能的,但是有一個弊端(快來評論區回復呀)。
通過高度計算,然后分通道,避免 absolute
因為上一個方案是按下標分類的,其實瀑布流是按高度分類的,所以我們分類條件換成最低的列。
jsGroupHeightList(){ var list = [ {height: 0, list: []},{height: 0, list: []}, {height: 0, list: []},{height: 0, list: []}, ] // 遍歷每個元素 for(var i = 0; i < this.list.length; i++){ // 當元素有大小的時候在進行操作。 if(!this.listInfo[i].height) return list; // 默認第一個通道是最小高度列 var minHeightItem = list[0]; // 計算最小高度列 list.forEach(v=>{ if(v.height < minHeightItem.height) minHeightItem = v }) // 把新的元素高度累加到列中。 minHeightItem.height += this.listInfo[i].height // 把新的元素push到列中 minHeightItem.list.push({idx: i, item: this.list[i]}) } return list; },
總結
好了,到這里我能想到的方案就都介紹了。你還有什么方案嗎?咱們可以在評論區討論一下可行性。接下來就是我們的方案總結了。
方案 | 優點 | 缺點 | 點評 |
---|---|---|---|
columns | 實現簡單、純 CSS 方案 | 兼容性 | – |
flex | – | 需要固定高度,填充難以控制等問題 | – |
float、inline、bootstrapGrid | – | – | 沒點大都用不出這方案 |
grid | – | – | 可以nth-child模擬實現、或者等待兼容性 masonry |
absolute | 效果好 | – | JS計算無限可能 |
js普通通道 | – | 填充難以控制 | – |
js優化通道 | 效果好、無絕對定位 | 在出現夸列等操作的時候不是很好控制 | – |