JavaScript实现瀑布流式布局

瀑布流,又称瀑布流式布局。视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。

 就是如下这样的布局:

这样式的布局,每张图片等宽不等高,在页面上从左到右依次排列,从第二行开始,每一张图片插入到前一行整列高度最小的下方,并且随着滚动条向下移动还会不断加载新的图片,还是像之前的布局一样插入。因此,实现瀑布流布局应该达到两种效果,一个是瀑布流样式的静态布局,另一个就是动态加载

静态布局

瀑布流的实现最基本的依据是通过定位,设定整体文档内容为相对定位,让每一个图片所在容器进行绝对定位。因为是有绝对定位,所以需要考虑 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");

猜你喜欢

转载自blog.csdn.net/bertZuo/article/details/83857986