手把手教你用原生JS實現瀑布流

一、什麼是瀑布流佈局

當我們瀏覽一些圖片網站時會發現一些圖片寬度相同、高度不同,確能夠自動地適應,達到一行一行展示的效果。並且當瀏覽器滑到底部時,新加載的圖片也會自動適應。這一效果就是瀑布流佈局。瀑布流佈局的特點是圖片等寬不等高

二、瀑布流佈局效果展示

  • 三列
    在這裏插入圖片描述
  • 四列在這裏插入圖片描述
  • 五列
    在這裏插入圖片描述

三、瀑布流佈局原理

實現瀑布流佈局最重要的就是怎麼用JS使子盒子定位到合適的位置
接下來我用下面這個示意圖向大噶解釋瀑布流的基本原理(圖醜別介意哈哈)

注意
(1)紅色盒子是按順序排在第一行的子盒子
(2)從第二行開始,將第二行的第一個子盒子追加在第一行最矮盒子的後面;將第二行的第二個子盒子追加在第一行第二矮矮盒子的後面…依次類推。 (下面第一張圖)
(3)新追加子盒子的位置要相對父盒子進行定位。其 左距離下標值 * 子盒子寬度上距離 爲其所跟子盒子的高度 (下面第二張圖)
(4)排列好第二行的子盒子後將前兩行同列子盒子的高度相加看作新的一行子盒子的高度,再重複上述步驟。 (下面第三張圖)

在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述

四、HTML頁面佈局

很簡單的一個頁面佈局,大噶都能看的懂哈(我這裏使用了十六張圖片),就不囉(tou)嗦(lan)啦!

<div id="main">
  <div class="box">
    <div class="pic">
      <img src="../program1/images/1.jpg" alt="">
    </div>
  </div>
  <div class="box"><div class="pic"><img src="../program1/images/2.jpg" alt=""></div></div>
  <div class="box"><div class="pic"><img src="../program1/images/3.png" alt=""></div></div>
  <div class="box"><div class="pic"><img src="../program1/images/4.png" alt=""></div></div>
  <div class="box"><div class="pic"><img src="../program1/images/1.jpg" alt=""></div></div>
  <div class="box"><div class="pic"><img src="../program1/images/2.jpg" alt=""></div></div>
  <div class="box"><div class="pic"><img src="../program1/images/3.png" alt=""></div></div>
  <div class="box"><div class="pic"><img src="../program1/images/4.png" alt=""></div></div>
  <div class="box"><div class="pic"><img src="../program1/images/1.jpg" alt=""></div></div>
  <div class="box"><div class="pic"><img src="../program1/images/2.jpg" alt=""></div></div>
  <div class="box"><div class="pic"><img src="../program1/images/3.png" alt=""></div></div>
  <div class="box"><div class="pic"><img src="../program1/images/4.png" alt=""></div></div>
  <div class="box"><div class="pic"><img src="../program1/images/1.jpg" alt=""></div></div>
  <div class="box"><div class="pic"><img src="../program1/images/2.jpg" alt=""></div></div>
  <div class="box"><div class="pic"><img src="../program1/images/3.png" alt=""></div></div>
  <div class="box"><div class="pic"><img src="../program1/images/4.png" alt=""></div></div>
</div>

五、CSS樣式佈局

這裏提一些大家需要注意的地方
(1)選擇器main 要設置 position: relative; 是爲了後續一部分子盒子要進行定位操作(子絕父相)
(2)選擇器box 要設置 float: left; 是因爲 div 標籤是塊狀元素,使用 float: left; 使其左浮

<style>
  * {
    padding: 0;
    margin: 0;
    border: none;
  }
  img {
    width: 200px;
    vertical-align: top;
  }
  #main {
    position: relative;
  }
  .box {
    float: left;
    padding: 15px 0 0 15px;
  }
  .pic {
    padding: 10px;
    border: 1px solid #ccc;
  }
</style>

六、JS核心代碼

1、全部JS代碼展示

window.addEventListener("load",function() {
  //1、實現瀑布流佈局
  waterFall('main','box');
  })
})

/*
  實現瀑布流佈局,傳遞參數爲string類型
*/
function waterFall(parent,child) {
  //1、根據圖片的列數來確定父盒子的寬度,父盒子居中
  //1.1 獲取標籤,父盒子和所有子盒子
  var father= document.getElementById(parent);
  var allBox = document.getElementsByClassName(child); 
  //1.2 獲取其中一個的寬度
  var boxWidth = allBox[0].offsetWidth;
  //1.3 獲取文檔的寬度(兼容)
  var screen = document.documentElement.clientWidth || document.body.clientWidth;
  //1.4 求出當前圖片的列數,是變化的
  var cols = parseInt(screen / boxWidth);
  //1.5 父盒子居中,給父盒子設置寬度
  father.style.width = cols * boxWidth + 'px';
  father.style.margin = '0 auto';

  //2、子盒子定位(從第二行開始)
  //2.1 定義變量
   var heightArr = [], boxHeight = 0, minBoxHeight = 0, minIndex = 0;
  //2.2 遍歷所有的盒子
  for(let i = 0;i < allBox.length; i++) {
    boxHeight = allBox[i].offsetHeight;
    //2.3 判斷是否是第一行
    if(i < cols) {
      heightArr.push(boxHeight)
    }else { //剩餘行做定位
      //2.4 取出數組中最矮盒子的高度
      minBoxHeight = heightArr[minBox(heightArr)];
      //2.5 取出最矮盒子再數組中的索引
      minIndex = minBox(heightArr);
      //2.6 剩餘子盒子的定位
      allBox[i].style.position = 'absolute';
      allBox[i].style.left = minIndex * boxWidth + 'px';
      allBox[i].style.top = minBoxHeight + 'px';
      //2.7 更新高度
      heightArr[minIndex] += boxHeight;
    }
  }
  
}

function minBox(box) {
  var j = 0;
  for(i in box) {
    if(box[j] > box[i])
      j = i
  }
  return j;
} 

2、JS代碼詳解----入口函數

在入口函數中調用函數 waterFall() 實現瀑布流佈局。傳遞兩個參數,都爲 String 類型。第一個參數是父盒子的選擇器名,第二個參數是子盒子的選擇器名。

window.addEventListener("load",function() {
  //1、實現瀑布流佈局
  waterFall('main','box');
  })
})

3、JS代碼詳解----父盒子居中

在css代碼中我們並沒有讓父盒子居中,是因爲父盒子寬度是隨着瀏覽器寬度的改變而改變的。因此在 waterFall() 實現瀑布流函數中我們首先要做的就是使父盒子居中。

這裏提一些大家需要注意的地方
(1)獲取文檔的寬度時推薦寫兼容寫法(大噶可以參考:Scroll家族(寫法類似)
(2)boxWidth 是每一個子盒子的寬度,因爲瀑布流佈局的特點是圖片的寬度相同的,高度不同,所以每一個子盒子的寬度都是相同的。
(3)設置父盒子的寬度,其寬度即子盒子所佔列數 * 子盒子的寬度
(4)設置父盒子 margin = '0 auto’ 達到父盒子居中的效果

註釋部分需要大噶特別注意

//1、根據圖片的列數來確定父盒子的寬度,父盒子居中
  //1.1 獲取標籤,父盒子和所有子盒子
  var father= document.getElementById(parent);
  var allBox = document.getElementsByClassName(child); 
  //1.2 獲取其中一個的寬度
  var boxWidth = allBox[0].offsetWidth;
  //1.3 獲取文檔的寬度(兼容)
  var screen = document.documentElement.clientWidth || document.body.clientWidth;
  //1.4 求出當前圖片的列數,是變化的
  var cols = parseInt(screen / boxWidth);
  //1.5 父盒子居中,給父盒子設置寬度
  father.style.width = cols * boxWidth + 'px';
  father.style.margin = '0 auto';

4、JS代碼詳解----子盒子定位

這裏提一些大家需要注意的地方
(1)根據瀑布流佈局的原理,我們需要把每一列子盒子的高度存放在一個數組中,即 heightArr,將新的子盒子每次追加在最小高度的子盒子的後面。這一步驟使用 for循環* 和 if判斷 語句就可以實現
(2)函數 minBox(box) 可以傳遞一個數組類型的參數,作用是找到數組中的最小值,並且特別注意返回的是最小值的下標
(3)設置新的子盒子的定位,首先要設置 position = ‘absolute’; (子絕父相)。其 left 即爲 下標值 * 子盒子寬度top 值爲其所跟子盒子的高度。
(4)更新高度作爲該一列的高度。

註釋部分需要大噶特別注意

  //2、子盒子定位(從第二行開始)
  //2.1 定義變量
   var heightArr = [], boxHeight = 0, minBoxHeight = 0, minIndex = 0;
  //2.2 遍歷所有的盒子
  for(let i = 0;i < allBox.length; i++) {
    boxHeight = allBox[i].offsetHeight;
    //2.3 判斷是否是第一行
    if(i < cols) {
      heightArr.push(boxHeight)
    }else { //剩餘行做定位
      //2.4 取出數組中最矮盒子的高度
      minBoxHeight = heightArr[minBox(heightArr)];
      //2.5 取出最矮盒子再數組中的索引
      minIndex = minBox(heightArr);
      //2.6 剩餘子盒子的定位
      allBox[i].style.position = 'absolute';
      allBox[i].style.left = minIndex * boxWidth + 'px';
      allBox[i].style.top = minBoxHeight + 'px';
      //2.7 更新高度
      heightArr[minIndex] += boxHeight;
    }
  }
function minBox(box) {
  var j = 0;
  for(i in box) {
    if(box[j] > box[i])
      j = i
  }
  return j;
} 

七、升級版

上面已經實現了瀑布流佈局,但發現瀏覽器中16張圖片展示完後不會再有圖片展示。在這裏將在以上代碼的基礎上用Dock數據加載解決這個問題(實際中用ajax)

1、入口函數更改

在入口函數中 增加瀏覽器滾動的監聽器 ,其內完成加載新的數據的功能
在實現瀑布流函數後增加自定義追加 check()函數scroll(0兼容性函數

這裏提一些大家需要注意的地方
(1)在瀏覽器滾動的監聽器首先要使用 check()函數 檢查是否需要增加 新數據
(2)在這裏使用的是一個數組(實質是假數據)實現新數據加載的功能
(3)遍歷數組,創造新的節點追加到瀏覽器中
(4)追加新數據後重新進行瀑布流佈局

window.addEventListener("load",function() {
  //1、實現瀑布流佈局
  waterFall('main','box');

  //2、加載數據(追加)
  window.addEventListener('scroll',function() {
    if(check()) {
      //2.1 假數據
      var dataArr = [
        {"src":"../program1/images/1.jpg"},
        {"src":"../program1/images/2.jpg"},
        {"src":"../program1/images/3.png"},
        {"src":"../program1/images/4.png"},
      ];
      //2.2 遍歷數據
      for(var i = 0;i < dataArr.length; i++) {
        var newBox = document.createElement('div');
        newBox.className = 'box';
        document.getElementById('main').appendChild(newBox);

        var newPic = document.createElement('div');
        newPic.className = 'pic';
        newBox.appendChild(newPic);

        var newImg = document.createElement('img');
        newImg.src = dataArr[i].src;
        newPic.appendChild(newImg);
      }
      
      //重新進行瀑布流佈局
      waterFall('main','box');
    }
  });
});

2、追加檢查函數和scroll兼容性寫法

(1)check()函數true 的條件是 數組最後一個元素的高度 + 盒子高度的一半 <= 頁面高度 + 頁面滾出瀏覽器的高度(偏移高度)
(2)scroll()函數 的作用是得到頁面滾出瀏覽器的高度的兼容性寫法推薦參考:Scroll家族

//追加在最矮盒子的後面
function check() {
  //1、獲取最後的盒子
  var allBox = document.getElementsByClassName('box');
  var lastBox = allBox[allBox.length - 1];
  //2、 最後盒子高度的一半
  var lastBoxDis = lastBox.offsetHeight*0.5 + lastBox.offsetTop;

  //3、頁面高度
  var screenH = document.documentElement.clientHeight || document.body.clientHeight;

  //4、求出頁面滾出瀏覽器的高度(偏移高度)
  var scrollTop = scroll().top;

  //5、返回結構
  return lastBoxDis <= screenH + scrollTop;
  
}

//兼容性
function scroll() {
  if(window.pageYOffset != null){
      //返回一個字面量對象(JSON對象,JSON中的鍵必須有雙引號)
      return {
          "top":window.pageYOffset,
          "left":window.pageXOffset
      }
  }else if (document.compatMode === 'CSS1Compat'){     //W3C
      return {
          "top":document.documentElement.scrollTop,
          "left":document.documentElement.scrollLeft
      }
  }else {
      return {
          "top":document.body.scrollTop,
          "left":document.body.scrollLeft
      }
  }
}

大噶若覺得這篇blog有幫助到你,走過路過點個👍唄
若有啥地方需要改進,也請大佬多多提點

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章