最近遇到这么一个需求,须要在手机上作一个两列的瀑布流布局,后来就把这个问题研究了一下,作个记录。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