瀑布流,又称瀑布流式布局。视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。
就是如下这样的布局:
这样式的布局,每张图片等宽不等高,在页面上从左到右依次排列,从第二行开始,每一张图片插入到前一行整列高度最小的下方,并且随着滚动条向下移动还会不断加载新的图片,还是像之前的布局一样插入。因此,实现瀑布流布局应该达到两种效果,一个是瀑布流样式的静态布局,另一个就是动态加载。
静态布局
瀑布流的实现最基本的依据是通过定位,设定整体文档内容为相对定位,让每一个图片所在容器进行绝对定位。因为是有绝对定位,所以需要考虑 top / left 的值。首先,确定HTML结构和CSS样式。
HTML结构,
<div id="main">
<div class="box"><div class="pic"><img src="images/0.jpg"></div></div>
<div class="box"><div class="pic"><img src="images/1.jpg"></div></div>
.
.
.
<div class="box"><div class="pic"><img src="images/22.jpg"></div></div>
</div>
CSS样式,
*{margin: 0; padding: 0;}
#main{position: relative;}
.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{height: auto; width: 186px;}
由此可见,每张图片保存在一个pic盒子中,pic又放在了box盒子中,设定了最外围大容器main相对定位,所有的box盒子根据main进行绝对定位。下面就是JS部分,按照一个编写思路进行。
第1步:我们知道瀑布流布局是通过定位每一个box元素布局的,那可以将其封装成一个函数,可想而知这个函数会接受两个参数:一个参数是得确定需要布局的元素来自哪里,也就是确定它的父元素是谁;另一个参数就是我们需要布局的元素。
waterfall("main","box");
waterfall函数
第2步:编写这样一个waterfall函数,由上而知来设定这个函数的两个形参,就叫做parent 和 box。
function waterfall(parent,box){}
第3步:获取父元素。
var oParent = document.getElementById("main");
第4步:将父元素main下所有class为box元素取出,可以将获取指定class名称元素这样的功能封装成一个函数,可想而知这个函数接受两个参数:一个参数是得确定需要获取的元素来自哪里,也就是确定它的父元素是谁;另一个参数就是我们需要获取的指定元素。
var oBoxs = getByClass(oParent,box);
getByClass函数
第5步:编写从父元素获取它的所有class为box的子元素的函数。
function getByClass(parent,clsName){}
第6步:HTML中class名是可以多用的,也就是说获取到的结果会是一个数组,因此需要定义一个新数组用来存储获取到的所有class为box的元素。
var boxArray = new Array();
第7步:既然是要找到指定的子元素,那就需要先获取所有的子元素,之后在进行筛选。
var oElement = parent.getElementsByTagName("*");
第8步:循环遍历所有子元素通过判断获取到class为box的子元素,最后需要将该函数结果返回。
for(var i=0; i<oElement.length; i++){
if(oElement[i].className == clsName){
boxArray.push(oElement[i]);
}
}
return boxArray;
第9步:现在已经得到了所有的box元素,那么下面就该考虑如何排列显示这些元素。首先得确定整个页面需要多少列显示图片,列数 = 页面宽度 / 每个图片的宽度
第10步:每个box元素的宽度。
var oBoxWidth = oBoxs[0].offsetWidth;
第11步:页面的总宽度。
var pageWidth = document.documentElement.clientWidth;
第12步:知道了每个box元素的宽度和页面的总宽度,就可以计算出列数,但是页面单位是像素,是只接受整数的。
var cols = Math.floor(pageWidth/oBoxWidth);
第13步:确定了列数,但在改变浏览器大小时,列数会改变,这是因为没有确定整体 main 的宽度。设置main的宽度,并居中。
oParent.style.cssText="width:"+oBoxWidth*cols+"px;margin:0 auto;";
第14步:已经是确定了布局中的列数,接下来就是要确定绝对定位中的 top / left 值。然而在第一行,图片的排列是不需要定位的,但是这一行的图片高度却是很重要的,因为它就是下面一行图片定位所需要的 top 值,不能忘了瀑布流布局的特点之一就是从第二行开始,每一张图片插入到前一行整列高度最小的下方。
第15步:想要确定第一行图片的高度,先定义一个新数组用来存放每一列高度的数组。
var heightArr=[];
第16步:那如何知道所有的box元素哪些是排列在第一行的?因此要遍历所有的box元素数组,如果其中数组下标小于列数的不就是在第一行的嘛,比较是顺序排列的。
for(var i=0; i<oBoxs.length; i++){
if(i<cols){
heightArr.push(oBoxs[i].offsetHeight)
}
}
第17步:现在已经存放了第一行图片在数组里,也就是说数组中存放的每一列高度就是第一行图片的高度。else 里就得考虑从第二行开始,图片插入位置的问题了。
第18步:之前已经说过了,需要将图片插入上一行中高度最小的下面,那就需要找到这个高度最小图片的位置。计算一行图片中最低的高度值需要用到 Math.min方法,但这个方法只能作用一组数据,不能用于数组。而apply可以改变方法中的this指向了数组。
var minHeight = Math.min.apply(null,heightArr);
getMinIndex函数
第19步:至此算是确定了 top 值,然后就是 left 值了。想要确定 left 值就必须要知道这个高度最小的图片所在的位置,之前只是找到了高度最小的图片,却不知道这个图片在哪里。所以就需要这个目标图片的索引值,可以将其封装成一个函数。
var index = getMinIndex(heightArr,minHeight);
第20步:获取高度最低值的索引值的函数,它接受两个参数:需要查找的数组,目标元素。
function getMinIndex(arr,val){
for(var i in arr){
if(arr[i]==val){
return i;
}
}
}
第21步:找到高度最低的图片位置,就可以计算当前图片距离整个页面左边的距离(x轴距离 / left 值),也就是每个图片的宽度 * 索引值,因为当前图片和页面左边界也就是隔了 index 数值大小的图片数。
var distLeft = oBoxWidth*index;
第22步:知道了top / left 值,就可以对下一行的图片进行绝对定位了。
oBoxs[i].style.position ="absolute";
oBoxs[i].style.top = minHeight+"px";
oBoxs[i].style.left = distLeft+"px";
第23步:此时,会存在一个bug。因为我们之前存放在数组中的高度值是第一行的图片高度,这个数据是固定不变的,也就是在找第一行图片高度最小时插入新图片,所找的第一行图片永远是那高度最小的一个,后来的所有图片都会插入到他的下面,所以呢,我们需要在插入新图片时更新数组中的高度值。
heightArr[index] += oBoxs[i].offsetHeight;
动态加载
瀑布流的静态布局已经完成。现在就要实现第二个效果:随着滚动条向下移动还会不断加载新的图片。
第24步:动态加载用到了滚条事件。
window.onscroll = function(){}
checkScrollSlide函数
第25步:首先,需要确定的是滚条滚到何时才可以加载新图片。因此,我们需要检测是否具备了滚条加载数据块的条件,以可视窗口最后一个图片出现了一半为准。定义一个函数实现这样的功能。
function checkScrollSlide(){}
第26步:还是先获取父元素,在获取其下的子元素,这里就可以调用 getByClass 函数来获取 box 元素。
var oParent = document.getElementById("main");
var oBoxs = getByClass(oParent,"box");
第27步:需要确定这样一个范围,可以比较两个高度值,一个是所有box元素中最后一个出现的图片占据整个页面的高度值+自身图片高度值的一半,
var lastBoxHeight = oBoxs[oBoxs.length-1].offsetTop + Math.floor(oBoxs[oBoxs.length-1].offsetHeight/2);
第28步:另一个是相对于最后一个图片,整个页面滚动的距离,这等于页面相对于浏览器可视窗口滚走的距离+可视窗口的高度。
第29步:页面滚走距离,存在标准模式与混杂模式
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
第30步:浏览器可视窗口高度
var height = document.body.clientHeight || document.documentElement.clientHeight;
第31步:现在已经有了两个高度值,两者比较,当lastBoxheight < scrollTop+height 时,就具备了加载数据块的条件。
return(lastBoxHeight<scrollTop+height)?true:false;
第32步:在满足判断条件之后,就可以动态加载数据了,现模拟一下后台数据块,json表示。
var dataInt={"data":[{"src":"0.jpg"},{"src":"1.jpg"},{"src":"2.jpg"}]};
第33步:准备工作算是完成了。开始正式实现第二个效果,首先还是获取父元素。
var oParent = document.getElementById("main");
第34步:将数据块渲染到当前页面的尾部。则需要先将数据块遍历一遍,依次将新元素添加。
for(var i=0; i<dataInt.data.length; i++){}
第35步:添加就要考虑添加在哪里,根据HTML结构,首先得需要有一个div盒子,然后是一个pic盒子,才能存放新加载的图片
第36步:创建一个 div 元素,将其class命为box,再把该元素插入到父元素最后。
var oBox = document.createElement("div");
oBox.className = "box";
oParent.appendChild(oBox);
第37步:创建pic盒子
oPic = document.createElement("div");
oPic.className = "pic";
oBox.appendChild(oPic);
第38步:创建img元素
var oImg = document.createElement("img");
oImg.src = "images/" + dataInt.data[i].src;
oPic.appendChild(oImg);
第39步:以上完成了动态加载,但是新加载的图片并没有按照瀑布流进行布局,所以调用waterfall函数来确定新加载图片该插入的位置。
waterfall("main","box");