JS原生实现瀑布流布局:核心算法与滚动加载详解

作者:交互手记时间:2026-10-23 06:50:35浏览:273

本文详细解析如何使用原生JavaScript实现瀑布流布局。重点涵盖获取元素、自适应计算列数、利用Math.min.apply定位图片位置,以及通过监听滚动事件实现数据动态加载。文章提供完整代码逻辑与关键函数说明,帮助开发者掌握无依赖库的瀑布流实现方案。

瀑布流布局实现概述

本文基于慕课网的瀑布流教程进行代码注释与思路梳理。该教程讲解细致,适合具备JavaScript基础知识的读者学习。虽然涉及的方法并不复杂,但自行编写时容易因思路不清而受阻。下文将重点解析几个核心方法的实现细节,建议结合慕课视频深入理解。HTML与CSS部分相对简单,主要难点在于JavaScript的逻辑处理。


<div id="main">//一个main包含了整个页面内容,box用来做一个外容器,img作为图片容器
  <div class="box">
   <div class="pic">
    <img src="img/0.jpg">
   </div>
  </div>
  <div class="box">
   <div class="pic">
    <img src="img/0.jpg">
   </div>
  </div>
 <div>

 //CSS
 //这里面的填充用padding值,因为offsetHeight是包括一个padding而不包括margin的方便以后高度的测量
*{
 margin: 0px;
 padding: 0px;
}
#main{
 position: relative;//在main上设置相对定位,在JS给box设置绝对定位,来控制图片位置
}
.box{
 padding: 15px 0 0 15px; 
 float: left;
}
.pic{
 padding: 10px;
 border: 1px solid #ccc;
 border-radius:5px;
 box-shadow: 0 0 5px #ccc;
}
.pic img{
 width: 168px;//瀑布流统一宽度但不同一高度,高度为自适应就好
 height: auto;
}

JavaScript核心逻辑

接下来进入JavaScript代码的具体实现部分。

获取元素与初始化

在编写JS逻辑前,需明确需求:是否需要封装函数以便复用,以及执行步骤的顺序。首要任务是获取所有表示内容块的元素(即class为box的元素)。因为后续的所有布局处理(包括图片尺寸调整)都是基于这些box进行的,所以第一步应封装一个获取所有box元素的函数。


//根据class获取元素
function getByClass(parent,clsName){//传入父元素和想要的class
 var boxArr=new Array//用来存储获取到所有class为box的元素,可以用对象的方式也可以用[]创建
 oElements=parent.getElementsByTagName('*');//取出所有子元素
 for (var i = 0; i < oElements.length; i++) {//我们需要遍历所有的子元素
  if(oElements[i].className==clsName)//如果我们找到了相应的子元素
   boxArr.push(oElements[i]);//取出传过来相等的className
   //push是数组自带的方法
 };
 return boxArr;返回数组
}

自适应列数计算

获取到class为box的元素集合后,下一步是确定第一排能容纳多少个元素。由于main容器的宽度未固定,页面是自适应的,当窗口缩小或放大时,每排显示的元素数量会随之变化。为了保证代码的通用性,使其能适配不同尺寸的图片,我们需要手动计算当前页面宽度下能排列多少列。计算方法是用页面总宽度除以单个box的宽度。

代码实现如下: var oBoxW=oBoxs[0].offsetWidth;//box的宽,提到了里面计算了padding的值 var clos=Math.floor(document.documentElement.clientWidth/oBoxW)//求列数 这样我们就求出了列数,无论图片的宽度如何变,我们都可以算出来 floor是用来取整数的,因为我们的px是没有浮点数类型的避免计算错误,所以取整 oparent.style.cssText='width:'+oBoxW*clos+'px; margin :0 auto'; 设置main的宽度

图片定位与最小高度算法

确定列数后,第一排图片的高度通常不一致。为了实现瀑布流效果,第二排及之后的图片需要放置在第一排中高度最小的图片下方。具体操作是:遍历后续图片,找到第一排中高度最小的那个box,将下一张图片(例如第8张,若第一排有7张)定位到该最小高度box的下方。这里涉及如何求数组最小值的问题。Math.min函数通常接收具体数值,无法直接传入数组。此时可以利用apply方法扩展作用域,将数组作为参数传递给Math.min,从而间接获取最小高度值。


var hArr=[];//每一列高度的值
 for (var i = 0; i < oBoxs.length; i++) {
  if(i<clos){//先记录好第一排高度
   hArr.push(oBoxs[i].offsetHeight);//放进去
  }else{//如果到达第二排,就开始计算
   var minH=Math.min.apply(Math,hArr);最小值计算
   //为什么可以呢,本来不能传数组,我们通过apply绑定作用域在Math上,也就是说还是执行这个函数但是,apply第二个参数需要是数组,所以间接地把参数变成了数组传进去,也就是说我们还是用了Math函数但值变成了数组里的值。
   //console.log(minH)
   var index=getMinhIndex(hArr,minH)//获取索引
   oBoxs[i].style.position="absolute";我们就可以找到,传入数组和最小值
   oBoxs[i].style.top=minH+"px";那下一张图片的高度救球出来了
   oBoxs[i].style.left=oBoxW*index+"px";左边的距离就是索引乘以元素的宽
   hArr[index]+=oBoxs[i].offsetHeight;//我们添加了一个图片后需要刷新数组里的高度,再去寻找下一个最小的,再向他的下面添加图片,一次循环
  }
 };
function getMinhIndex (arr,val){
 for(var i in arr){//在其中找到最小值,返回索引
  if(arr[i]==val){
   return i;
  }
 }
}

滚动加载与数据渲染

上述逻辑实现了基本的瀑布流样式。接下来需要处理滚动条拖动时的数据加载。这部分通常需要后端接口支持,慕课教程中采用了伪数据模拟。由于文字描述难以完全展现动态效果,建议参考慕课视频中的图解部分,以便更清晰地理解滚动加载的实现原理。


var dataInt={"data":[{"src":'0.jpg'},{"src":'1.jpg'},{"src":'2.jpg'},{"src":'3.jpg'}]}//我们自己写一个json数据
 window.onscroll=function(){
  if(checkScrollSlide){
   var oparent=document.getElementById("main")
   //将数据快渲染到当前页面尾部
   for (var i = 0; i < dataInt.data.length; i++) {//添加
    var oBox=document.createElement("div");
    oBox.className="box";
    oparent.appendChild(oBox);
    var opic=document.createElement('div');
    opic.className='pic';
    oBox.appendChild(opic);
    var oimg=document.createElement('img');
    oimg.src='img/'+dataInt.data[i].src;//将数据加进去
    opic.appendChild(oimg)
   }
   waterfall('main','box');//一会看完整代码,这就是刚才那些的封装
  }

 }
 function checkScrollSlide(){//检测是否具备加载数据块的条件
 var oparent=document.getElementById('main');
 var oBoxs=getByClass(oparent,'box')
 var lastBoxH=oBoxs[oBoxs.length-1].offsetTop+Math.floor(oBoxs[oBoxs.length-1].offsetHeight/2)求出最后一个图片的一半距离页面顶端的距离
 var scrollTop=document.body.scrollTop||document.documentElement.scrollTop;
 var height=document.body.clientHeight||document.documentElement.clientHeight;
 return(lastBoxH<scrollTop+height)?true:false;//如果页面距离顶部(包括滚轮)的距离比滚轮加页面距离小,那么就返回true
}

以下是完整的JavaScript代码实现。


window.onload=function(){
 waterfall('main','box');
 //json
 var dataInt={"data":[{"src":'0.jpg'},{"src":'1.jpg'},{"src":'2.jpg'},{"src":'3.jpg'}]}
 window.onscroll=function(){
  if(checkScrollSlide){
   var oparent=document.getElementById("main")
   //将数据快渲染到当前页面尾部
   for (var i = 0; i < dataInt.data.length; i++) {
    var oBox=document.createElement("div");
    oBox.className="box";
    oparent.appendChild(oBox);
    var opic=document.createElement('div');
    opic.className='pic';
    oBox.appendChild(opic);
    var oimg=document.createElement('img');
    oimg.src='img/'+dataInt.data[i].src;
    opic.appendChild(oimg)
   }
   waterfall('main','box');
  }

 }
}
function waterfall(parent,box){
 //将main下所有class为box的元素取出来
 var oparent=document.getElementById(parent);
 var oBoxs=getByClass(oparent,box)
 //console.log(oBox.length)
 //计算整个页面的列数(页面宽/box宽)
 var oBoxW=oBoxs[0].offsetWidth;
 //console.log(oBoxW);
 var clos=Math.floor(document.documentElement.clientWidth/oBoxW)//求列数
 //console.log(clos)
 //设置main的宽
 oparent.style.cssText='width:'+oBoxW*clos+'px; margin :0 auto';
 var hArr=[];//每一列高度的值
 for (var i = 0; i < oBoxs.length; i++) {
  if(i<clos){
   hArr.push(oBoxs[i].offsetHeight);
  }else{
   var minH=Math.min.apply(Math,hArr);
   //console.log(minH)
   var index=getMinhIndex(hArr,minH)//索引
   oBoxs[i].style.position="absolute";
   oBoxs[i].style.top=minH+"px";
   oBoxs[i].style.left=oBoxW*index+"px";
   hArr[index]+=oBoxs[i].offsetHeight;//更改数组
  }
 };
 //console.log(hArr)
}
//根据class获取元素
function getByClass(parent,clsName){
 var boxArr=new Array//用来存储获取到所有class为box的元素
 oElements=parent.getElementsByTagName('*');//取出所有子元素
 for (var i = 0; i < oElements.length; i++) {
  if(oElements[i].className==clsName)
   boxArr.push(oElements[i]);//取出传过来相等的className
 };
 return boxArr;
}
function getMinhIndex (arr,val){
 for(var i in arr){
  if(arr[i]==val){
   return i;
  }
 }
}
function checkScrollSlide(){//检测是否具备加载数据块的条件
 var oparent=document.getElementById('main');
 var oBoxs=getByClass(oparent,'box')
 var lastBoxH=oBoxs[oBoxs.length-1].offsetTop+Math.floor(oBoxs[oBoxs.length-1].offsetHeight/2)
 var scrollTop=document.body.scrollTop||document.documentElement.scrollTop;
 var height=document.body.clientHeight||document.documentElement.clientHeight;
 return(lastBoxH<scrollTop+height)?true:false;
}

瀑布流效果的实际运行截图。

瀑布流布局实现概述

以上即为JS原生瀑布流效果的详细实现整合,希望对读者有所帮助。如有任何疑问,欢迎留言讨论,小编会及时回复。感谢对本站的支持!

作者最新文章