最近遇到這麼一個需求,須要在手機上作一個兩列的瀑布流佈局,後來就把這個問題研究了一下,作個記錄。javascript
通常來說,這種佈局能夠分爲兩種狀況:css
圖片的數量是必定的,不須要頁面滾動到底部時,再動態加載圖片,只須要將圖片排成若干列html
圖片的數量的不定的,頁面觸底時,須要從遠程加載圖片。java
前者使用css的方法便可解決,後者則須要js來幫忙。算法
當咱們展現的圖片數量必定時,能夠優先採用css解法。其中一種方法是藉助css的多欄佈局:segmentfault
.photos{ column-count: 3; column-gap: 10px; }
獲得的效果以下:數組
flex佈局一樣能夠作到這一點,訣竅在於將flex-direction
設爲column
;可是相對於多列布局,須要根據瀑布流的列數,計算一個合適的容器高度,否則可能會致使多出一行。若是你在下面的demo 中,看到了4列,不要懷疑,就是我計算的容器高度不合適致使的。。。函數
當圖片須要動態插入時,上面的兩種方法就不合適了,由於他們本質上是將圖片按照縱向進行排列的。圖片動態插入時一般咱們但願圖片是按橫向插入到容器中的。這時候就須要js來幫忙了。首先,咱們看看瀑布流和揹包問題的關係。佈局
瀑布流的基本思路是將一堆圖片放到若干列中,列與列之間的高度比較均勻,而不會相差太大。假如咱們要分紅兩列,那麼,問題就變成了,從 n 張圖片中挑出 m 張,使這 m 張圖片的總高度儘可能接近 n 張圖片總高度的 1 / 2。因而這就變成了一個揹包問題flex
揹包問題是啥這裏不作展開,說白了是將一個複雜的問題分解爲幾個簡單的問題,大佬們講的都比我好,網上也有各個語言版本的實現,不太瞭解的同窗能夠查看上面的連接。這裏直接放一個函數
function dp(ws, vs, limit) { let len = ws.length; let tables = new Array(len).fill().map(x => []) tables[-1] = new Array(limit + 1).fill(0); for(let i = 0; i < len; i++) { for (let w = 0; w <= limit; w++) { if (ws[i] > w) { tables[i][w] = tables[i-1][w] } else { tables[i][w] = Math.max(tables[i-1][w], tables[i-1][w-ws[i]] + vs[i]) } } } // 回溯獲得應該選哪些 let max = limit; let selected = []; for (let idx = len - 1; idx >= 0; idx--) { if (ws[idx] <= max) { let isSelected = tables[idx-1][max] < tables[idx-1][max-ws[idx]] + vs[idx] if(isSelected) { selected.push(idx); max = max - ws[idx]; } } } return selected; }
有了這個解法以後,咱們也就不難寫出一個瀑布流佈局。具體思路是:假設咱們要作一個3列的瀑布流佈局,那麼能夠不斷從圖片數組中選出一組圖片,使圖片的高度接近總高度的1/3,最終獲得3組圖片。下面是一個代碼片斷
// colCount 表示要生成幾列 while(colCount--) { // 獲取被選出的照片索引 let idxs = dp(photoHeights, photoHeights, aver) // 獲得被選出的一組圖片 let photoCol = photos.filter((p,idx) => idxs.includes(idx)) this.cols.push(photoCol) photoHeights.forEach((v,i) => { if (idxs.includes(i)) { photoHeights[i] = null } }) }
下面這個demo就是按上面的思路實現的,能夠拖動下面的滑塊來改變列數,觀察底部的間隙。在使用揹包算法解決瀑布流問題時,一個須要咱們注意的地方是,要將圖片高度轉化成整數。
參考文章:http://www.javashuo.com/article/p-biqohbvu-h.html
http://www.javashuo.com/article/p-nwbghoyu-gr.html
http://www.javashuo.com/article/p-skcurxxk-b.html