瀑布流布局,一般指根據(jù)內(nèi)容高度自適應(yīng)填充到某一列以使整體頁面和諧,常見的有圖片網(wǎng)站,比如每一行每一列的內(nèi)容是錯(cuò)開的。
瀑布流布局一般是下面這個(gè)樣子
PC:
PC端

image.png
Tips:本文字?jǐn)?shù)稍長,思路清晰,耐心讀下來絕對讓你掌握瀑布流布局!
下面介紹三種實(shí)現(xiàn)方案,推薦大家仔細(xì)看看第一種
方案一: js實(shí)現(xiàn),css略微輔助
目標(biāo)要求:
- 實(shí)現(xiàn)瀑布流
- PC和移動(dòng)端都要適配
- 圖片不可變形,寬度固定
- 瀑布流的列數(shù)不固定,隨屏幕寬度的變化而變化
- 若屏幕寬度有富余,則圖片瀑布流區(qū)域居中展示。
思路分析
- 實(shí)現(xiàn)瀑布流思路如下:
(1)根據(jù)圖片寬度,計(jì)算當(dāng)前屏幕一共有幾列。
舉例:當(dāng)前屏幕寬度是820px,每一個(gè)包裹著圖片的盒子寬度是200px,所以一共可以排820/200 = 4 列(向下取整)
(2)先按順序排第一列,記錄下每一列當(dāng)前占據(jù)的高度值。
舉例:假設(shè)當(dāng)前屏幕寬度為820px,每一個(gè)包裹著圖片的盒子寬度是200px,可以排4列,現(xiàn)在有10個(gè)圖片高度分別是[120,55,800,380,77,20,130,600,453,780](單位px),先排前4個(gè)圖片,排完之后這4列高度依次是[120,55,800,380],把這4列當(dāng)前高度值存起來。
(3)第一列排完之后開始排后面的元素,要找出所有列數(shù)中高度最小的那一個(gè),排在它的下面,高度值累加。以此類推。
舉例:接(2)中的例子,現(xiàn)在這4列高度依次是[120,55,800,380],可以看到第2列高度最小,所以第5個(gè)圖片排在第2列,緊挨著第2列的第1個(gè)圖片下。然后第2列的高度變成了 55+77 = 132 ,當(dāng)前這4列的高度變成了[120,132,800,380]。剩下的圖片也按照這個(gè)邏輯去排就ok了。 - PC和移動(dòng)端適配在html中配置如下:
<meta name='viewport' content='width=device-width,initial-scale=1.0,user-scalable=no' />
- 圖片不變形,寬度固定
html中的img不是塊級元素,它屬于行內(nèi)替換元素,雖然width、height等屬性仍然有效,但非塊狀元素在用css或js修飾時(shí)會(huì)遇到很多不可預(yù)測的問題。基于此,我習(xí)慣給每一個(gè)img用一個(gè)div包裹住,以后直接操作外層div,如下
<div style="width:200px;height:auto">
<img src="xxxxx" alt='xxxxx' style="width:100%;height:auto"/>
</div>
- 第4個(gè)要求說白了是要監(jiān)聽瀏覽器窗口寬度變化,然后重新布局,所以給窗口對象添加一個(gè)resize 監(jiān)聽事件就ok了
window.addEventListener('resize',()=>{
// 重新布局
})
- 圖片區(qū)域居中顯示:如果針對單個(gè)圖片去計(jì)算位置以使整個(gè)區(qū)域居中顯示,這種方法計(jì)算量大,得不償失。所以將所有圖片包在一個(gè)div里,使最外層div居中就ok了。
代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>瀑布流布局</title>
<meta name='viewport' content='width=device-width,initial-scale=1.0,user-scalable=no' />
<style>
html{
font-size: 12px;
}
*{
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div class="wrap">
<div id='forcenter'></div>
</div>
<script src="./waterfall.js"></script>
<script>
let mwf = new WaterFall({
id: 'forcenter',
imgUrls: [
'https://icweiliimg6.pstatp.com/weili/l/195564498297815041.webp',
'https://weiliicimg9.pstatp.com/weili/l/461972764095807490.webp',
'https://icweiliimg9.pstatp.com/weili/l/447251256884854785.webp',
'https://icweiliimg6.pstatp.com/weili/l/165755629970653210.webp',
'https://weiliicimg6.pstatp.com/weili/l/218593064967209016.webp',
'https://icweiliimg6.pstatp.com/weili/l/320841925450530819.webp',
'https://icweiliimg1.pstatp.com/weili/l/165755655736786957.webp',
'https://icweiliimg1.pstatp.com/weili/l/452540045247774720.webp',
'https://weiliicimg9.pstatp.com/weili/l/223888416044482620.webp',
'https://icweiliimg9.pstatp.com/weili/l/292051943627161624.webp',
'https://icweiliimg1.pstatp.com/weili/l/235158650747486283.webp',
'https://weiliicimg1.pstatp.com/weili/l/165755681505542155.webp',
'https://icweiliimg1.pstatp.com/weili/l/202821919289376781.webp',
'https://weiliicimg9.pstatp.com/weili/l/611415730252415145.webp'
]
});
window.onload = ()=>{
mwf.WFRender();
}
window.onresize = ()=>{
mwf.resizeWF();
}
</script>
</body>
</html>
// waterfall.js
function WaterFall(obj){
this.id = obj.id;
this.imgUrls = obj.imgUrls;
this.container = document.getElementById(this.id);
this.container.className = 'forcenter';
}
WaterFall.prototype = {
WFRender: function(){
let flag = 0; // 標(biāo)志位,用于檢測每一張圖片都加載完畢
this.imgUrls.map((item,i)=>{
let div = document.createElement('div');
div.className = 'item';
let img = new Image();
img.src = item;
img.onload = () => {
flag++;
div.appendChild(img);
this.container.appendChild(div);
// 全部圖片加載完畢后再開始布局,否則獲取不到元素的最終高度。
if(flag===this.imgUrls.length){
this.resizeWF();
}
}
});
let styleE = document.createElement('style');
styleE.innerHTML = `div.item{
position: absolute;
transition: all .5s; /* 使過渡平滑*/
width : 200px;
height: auto;
padding:5px;
box-sizing: border-box; /* 非常重要,可將不必要的計(jì)算略去 */
}
div.item img{
width: 100%;
height: auto;
}
.forcenter{
position: relative;
margin: auto;
}`;
this.container.appendChild(styleE);
},
resizeWF: function(){
let winW = window.innerWidth;
let itemNum = Math.floor(winW/200); // 當(dāng)每一個(gè)項(xiàng)的寬度都是固定的時(shí)候,需要計(jì)算出瀏覽器一行可以排列幾個(gè)。
this.container.style.width = (itemNum*200)+'px'; // 用于居中的包裹盒子的寬度
let saveColumnHeight = []; // 定義一個(gè)數(shù)組,用于存儲(chǔ) 每一列所有元素的高度 之和
let items = document.querySelectorAll('.item');
for(let i=0; i< items.length; i++){
if(saveColumnHeight.length<itemNum){
saveColumnHeight[i] = items[i].offsetHeight;// 當(dāng)布局的元素還沒占滿一行時(shí),繼續(xù)向數(shù)組中添加第一行第i列的高度
setDiv(items[i],200*i,0); // 放置div
}else{ // 當(dāng)已經(jīng)占滿一行時(shí),就找出每一列的最小高度,然后當(dāng)前的這個(gè)div放在高度最小的那一列
let pos = getMinH(saveColumnHeight); //去找高度最小的那一列
saveColumnHeight[pos.column] += items[i].offsetHeight;
setDiv(items[i],pos.left,pos.top); // 放置div
}
}
}
}
// 工具性函數(shù),不必放入原型鏈。
function getMinH(arr){
let flag = {
left:0,
top:0,
column:0
}
arr.map((item,i)=>{
if(flag.top===0){
flag.top = item;
}else{
if(flag.top>item){ // 找出高度最小的那一列
flag.top = item;
flag.left = 200*i;
flag.column = i;
}
}
});
return flag;
}
function setDiv(item,left,top){
item.style.left = left+'px';
item.style.top = top+'px';
}
Tips
- 在布局之前,確保所有圖片已加載完畢,否則無法獲取最終高度導(dǎo)致布局出錯(cuò)。
- css中有一個(gè)屬性
box-sizing,它的作用是將元素的border和padding都計(jì)算在元素尺寸之內(nèi)(詳情請自己查閱),這么設(shè)置有一個(gè)好處:平常我們布局為了避免圖片緊挨著不好看經(jīng)常加一些padding或margin,因此計(jì)算過程就復(fù)雜了。box-sizing屬性可以將padding包含進(jìn)圖片尺寸,通過padding使圖片避免緊挨著,在計(jì)算上只考慮最外層尺寸就ok了,省了很多事。
方案二 純css
目標(biāo)要求
- 實(shí)現(xiàn)瀑布流樣式
- PC和移動(dòng)端都要適配
- 圖片不變形
- 瀑布流列數(shù)自適應(yīng)
思路分析
css中有這么兩個(gè)屬性:
-
column-count將元素劃分的列數(shù),若為auto,則自適應(yīng) -
column-gap元素之間的間隙 -
column-width每一列的寬度
使用這三個(gè)屬性基本可以實(shí)現(xiàn)啦!
代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>瀑布流布局</title>
<meta name='viewport' content='width=device-width,initial-scale=1.0,user-scalable=no' />
<style>
html{
font-size: 12px;
}
*{
margin: 0;
padding: 0;
}
div.item{
transition: all .5s;
/*width : 200px;*/
height: auto;
padding:5px;
box-sizing: border-box;
}
.wrap{
column-width: 200px;
column-count: auto;
column-gap: 1rem;
}
</style>
</head>
<body>
<div class="wrap" id='wrap'>
</div>
<script>
var imgUrls = [
'https://icweiliimg1.pstatp.com/weili/l/452540045247774720.webp',
'https://weiliicimg9.pstatp.com/weili/l/223888416044482620.webp',
'https://icweiliimg6.pstatp.com/weili/l/320841925450530819.webp',
'https://icweiliimg6.pstatp.com/weili/l/195564498297815041.webp',
'https://weiliicimg9.pstatp.com/weili/l/461972764095807490.webp',
'https://icweiliimg1.pstatp.com/weili/l/165755655736786957.webp',
'https://icweiliimg9.pstatp.com/weili/l/447251256884854785.webp',
'https://icweiliimg6.pstatp.com/weili/l/165755629970653210.webp',
'https://weiliicimg6.pstatp.com/weili/l/218593064967209016.webp',
'https://icweiliimg9.pstatp.com/weili/l/292051943627161624.webp',
'https://icweiliimg1.pstatp.com/weili/l/235158650747486283.webp',
'https://weiliicimg1.pstatp.com/weili/l/165755681505542155.webp',
'https://icweiliimg1.pstatp.com/weili/l/202821919289376781.webp',
'https://weiliicimg9.pstatp.com/weili/l/611415730252415145.webp'
];
window.onload = function(){
let container = document.getElementById('wrap');
imgUrls.map((item,i)=>{
let div = document.createElement('div');
div.className = 'item';
let img = document.createElement('img');
img.src = item;
img.style.width = '100%';
img.style.height = 'auto';
div.appendChild(img);
container.appendChild(div);
});
}
</script>
</body>
</html>
注意
其實(shí)這樣弄出來的根本不是嚴(yán)格意義上的瀑布流布局,只不過有一個(gè)瀑布流布局的樣子而已,因?yàn)閷Ω叨鹊呐袛嗖蝗鏹s計(jì)算來的嚴(yán)格,所以會(huì)出現(xiàn)一些不容易被注意到的問題(我也是測試了好久才找到),如下圖

image.png
這也是我推薦大家用第一種方法的最主要原因。
方案三 flex布局
這種方案做出來的效果有點(diǎn)奇葩,特殊的應(yīng)用場景可能會(huì)用到,最終效果是橫向瀑布流
flex布局中有一個(gè)屬性flex-flow,指明在哪個(gè)方向上在長度不夠的情況想拆行,詳情大家自己查閱
代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>瀑布流布局</title>
<meta name='viewport' content='width=device-width,initial-scale=1.0,user-scalable=no' />
<style>
html{
font-size: 12px;
}
*{
margin: 0;
padding: 0;
}
div.item{
transition: all .5s;
width : 200px;
height: auto;
padding:5px;
box-sizing: border-box;
}
.wrap{
display: flex;
flex-flow: column wrap;
height: 400px;
}
</style>
</head>
<body>
<div class="wrap" id='wrap'>
</div>
<script>
var imgUrls = [
'https://icweiliimg1.pstatp.com/weili/l/452540045247774720.webp',
'https://weiliicimg9.pstatp.com/weili/l/223888416044482620.webp',
'https://icweiliimg6.pstatp.com/weili/l/320841925450530819.webp',
'https://icweiliimg6.pstatp.com/weili/l/195564498297815041.webp',
'https://weiliicimg9.pstatp.com/weili/l/461972764095807490.webp',
'https://icweiliimg1.pstatp.com/weili/l/165755655736786957.webp',
'https://icweiliimg9.pstatp.com/weili/l/447251256884854785.webp',
'https://icweiliimg6.pstatp.com/weili/l/165755629970653210.webp',
'https://weiliicimg6.pstatp.com/weili/l/218593064967209016.webp',
'https://icweiliimg9.pstatp.com/weili/l/292051943627161624.webp',
'https://icweiliimg1.pstatp.com/weili/l/235158650747486283.webp',
'https://weiliicimg1.pstatp.com/weili/l/165755681505542155.webp',
'https://icweiliimg1.pstatp.com/weili/l/202821919289376781.webp',
'https://weiliicimg9.pstatp.com/weili/l/611415730252415145.webp'
];
window.onload = function(){
let container = document.getElementById('wrap');
imgUrls.map((item,i)=>{
let div = document.createElement('div');
div.className = 'item';
let img = document.createElement('img');
img.src = item;
img.style.width = '100%';
img.style.height = 'auto';
div.appendChild(img);
container.appendChild(div);
});
}
</script>
</body>
</html>
總結(jié)
第一種方法其實(shí)是最合適的,也是對開發(fā)人員要求最高的,即使有思路與算法在開發(fā)過程中也會(huì)踩很多坑,比如(1)圖片預(yù)加載,(2)querySelector取元素居然獲取不到,(3)使用box-sizing簡化計(jì)算過程降低復(fù)雜性,等等。box-sizing 屬性用好了非常高效,推薦大家去仔細(xì)看看
第二種方法其實(shí)也可行,但是不推薦
第三種方法就有點(diǎn)扯了,估計(jì)不常用到,大家看看就行