使用原生js实现瀑布流效果

题外话

话说最近博主是常常出去拍片,可是拍出来的片子如果硬生生扔到博客上的话,展示效果肯定会大打折扣。
正当博主望着窗外几个拿鞭炮炸汽车的熊孩子时,‘瀑布流’三个字在脑海中闪过。记得是某位大神
学长在某次公开课上展示过这么一个效果。于是百度了一下,最终在极客学院
上,跟着老师的视屏完成了这么一个效果,期间也是bug无数 >”<|||| 。

什么是瀑布流?

看到标题,你也许听过瀑布流,是的,这是目前各大网站常见的一个效果。比如花瓣网
或者美丽说等等,你会发现,如果你的鼠标向下滚动时,
这些页面会自动加载新的信息(一般为图像),从而实现了页面页面如‘瀑布般’无穷无尽,
永远会有新的东西刷出(可能是自动,也可能是手动),这就是所谓的瀑布流效果。

这种效果一般用于购物网站,摄影网站较多,这样可以在不打断用户的情况下,让用户不断
看到新的内容,有助于提升用户体验。

如何实现?

想要实现瀑布流,工具的话,当然是宇宙级开发语言———javascript啦 →__→,当然
你使用jquery等等js框架肯定也是可以的。

目前的话,主要有三种实现方法:

  • 传统的float布局方法(接下来我使用的就是这种)
  • 使用css3实现
  • 使用绝对定位实现

博主主要演示第一种方法,其他两种可自行百度。

实现思路

首先我们必须先知道瀑布流是一个什么样的原理。

简单的说,比如我现在有很多张图片,把它们扔到页面上是这样的:
pic
是不是感觉到左边还有很多空白很不舒服?那么,我们需要使用一种规则来重新分布图片,这就是
瀑布流插入图片的原理。

首先我从中随便取四张图片(当然,你取五张、六张也是可以的),就是这样:
pic

接下来我要依据瀑布流原理将剩下的图片插入

先找到已经出现图片中高度最矮的,也就是第四个,将下面一张图片插入,就是这样:
pic
然后高度最矮的就变成连第一张,那么下一张就应该茶道第一张后面,就是这样:
pic
以此类推,直达图片插入完成,就是一个瀑布流效果,像这样:
pic

姨?是不是注意到左下角还有一片空白?是图片插完了吗?这是一种可能,不过显然图片数量
远远不止这么多。其实是下面的图片还没有插进来,当你鼠标向下滚动时,后面的图片会自动插入
,到这里瀑布流的原理也就差不多流,接下来说说代码实现。

代码实现

首先来看看基本的布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
   <style>
*{
margin: 0;
padding:0;
}
.box{
padding: 5px;
float: left;
}
.img {

}
#container{
position: relative;
}
.appendClass{
width: 250px;
height: 150px;
background-color: #f8e530;
}
div{
font-family: 宋体;
font-size: x-large;
color: #0D3349;
text-align: center;
}
</style>
<body>
<div id="container">
<div class="box">
<div class="img" style="width: 250px;
height: 150px;
background-color: #00AA88;">
爱我你怕了吗</div>
</div>
<div class="box">
<div class="img" style=" width: 250px;
height: 300px;
background-color: #00CCFF;">来啊,打我啊</div>
</div>
<div class="box">
<div class="img" style="width: 250px;
height: 500px;
background-color: #ff6cf1;">大傻比</div>
</div>
<div class="box">
<div class="img" style=" width: 250px;
height:80px;
background-color: #b2dba1;">大坏蛋</div>
</div>
<div class="box">
<div class="img" style="width: 250px;
height: 200px;
background-color: #f5cef4;">死基佬</div>
</div>
<div class="box">
<div class="img" style=" width: 250px;
height: 300px;
background-color: #00CCFF;">no can no BB</div>
</div>
<div class="box">
<div class="img" style="width: 250px;
height: 500px;
background-color: #ff6cf1;">进逼</div>
</div>
<div class="box">
<div class="img1" style="width: 250px;
height: 150px;
background-color: #00AA88;">你行你上啊</div>
</div>
<div class="box">
<div class="img" style=" width: 250px;
height:80px;
background-color: #b2dba1;">因垂死听</div>
</div>
<div class="box">
<div class="img" style="width: 250px;
height: 200px;
background-color: #f5cef4;">阿哈哈哈</div>
</div>
<div class="box">
<div class="img" style="width: 250px;
height: 150px;
background-color: #00AA88;">活活慌慌呼呼</div>
</div>
</div>
</body>
```
这里面主要是完成了整个页面基本元素的插入,效果就像刚开始的那一张图,图片之间会有
大片空白。

可以看到,这里的每一个.box都设置了属性:float:left;因而图片实际上已经脱离的文档流,
它的位置是不固定的,如果你缩小窗口,图片的位置会随着窗口大小变化而变,因而接下来,
我首先要使用js来将图片的位置固定住。

```javascript
function fixImg(parent,childName){ //使得每一行有固定个数图片,不随窗口大小而改变
var oParent=document.getElementById(parent);
var imgArr=getChildren(oParent,childName);
// console.log(imgArr);
var imgWidth=imgArr[0].offsetWidth;
// console.log(imgWidth);
var cols=Math.floor(document.documentElement.clientWidth/imgWidth); //计算图片列数
// console.log(cols);
//根据图片修改外层div为某固定宽度
oParent.style.cssText="width:"+cols*imgWidth+"px;margin:0 auto;";

//开始把图片按照瀑布流规则插入
var imgHeight=[];
for(var i=0;i<imgArr.length;i++){
if(i<cols){ //图片已经显示
imgHeight[i]=imgArr[i].offsetHeight; //获得当前一行所有图片高度
}
else{
var minHeight=Math.min.apply(null,imgHeight); //找到最小高度
var minIndex=getLocation(imgHeight,minHeight);
imgArr[i].style.position='absolute';
imgArr[i].style.top=minHeight+'px'; //插入在最小高度图片下面
imgArr[i].style.left=imgArr[minIndex].offsetLeft+'px';
//插入图片的那部分高度改变
imgHeight[minIndex]=imgHeight[minIndex]+imgArr[i].offsetHeight;
}
}
}

可以看到,1-10行代码实现了固定图片的功能,主要原理就是修改了图片的的最外层div的宽度,
并使期居中对齐,而剩下的代码则是实现了 一次 插入图片的效果。
其中调用了外部定义的getChildren()和getlocation()方法,代码如下:

1
2
3
4
5
6
7
8
9
10
function getChildren(oParent,childName){   //获得图片个数
var imgArr=[];
var content=oParent.getElementsByTagName('*');
for(var i=0;i<content.length;i++){
if(content[i].className==childName){ //找到指定类名对元素
imgArr.push(content[i]);
}
}
return imgArr; //返回存放所有图片对数组
}

1
2
3
4
5
6
7
8
9
10
11
12
   function getLocation(aparent,oMinHeight){  //找到最小高度图片在数组对中对位置
for(var i=0;i<aparent.length;i++){
// console.log('imgHeight:'+aparent+' oMinHeight:'+oMinHeight);
if(aparent[i]==oMinHeight){
// console.log('i: '+i);
return i;
}
else{
console.log('not find');
}
}
}

到这里,你已经能够实现图片插入一次的效果,也就是说,所有的图片会在一瞬间按照
瀑布流的规则全部插入,那么,接下来所要做的就是实现随着鼠标的向下滚动,图片分批插入。

我们首先需要去监测鼠标滚动的距离,再据此设定一个图片加载的触发点,比如我这里就是设置为,
当最下面一张图片出现时,后面当图片开始加载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    var imgStyleHeight=[150,300,80,200,500];
var imgStyleColor=['#00AA88','#00CCFF','#b2dba1','#f5cef4','#ff6cf1'];
var imgStyleHtml=['you jump,i jump','岁月静好,妙香暖心','似乎很有趣','外星生物入侵啦'];
window.onscroll=function(){ //监测滚动条位置
var oParent=document.getElementById('container');
if(loadImg()){
for(var i=0;i<imgStyleHeight.length;i++){
var oDiv1=document.createElement('div');
oDiv1.className='box';
oParent.appendChild(oDiv1);
var oDiv2=document.createElement('div');
oDiv2.className='img';
// oDiv2.className='appendClass';
oDiv2.style.cssText="width:250px;height:"+imgStyleHeight[i]+"px;background-color:"+imgStyleColor[i]+";";
oDiv2.innerHTML=imgStyleHtml[i];
oDiv1.appendChild(oDiv2);
}
fixImg('container','box');
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    function loadImg (){    //滚动后图片的加载
var oParent=document.getElementById('container');
var aImg=getChildren(oParent,'box'); //获得已经插入的图片
var lastImgTop=aImg[aImg.length-1].offsetTop; //最后一张距离上方的高度
var scrollTop=document.documentElement.scrollTop||document.body.scrollTop;//滚动高度
var clientTop=document.documentElement.clientHeight||document.body.clientHeight; //当前窗口高度
// console.log(lastImgTop+'<'+scrollTop+'+'+clientTop);
if(lastImgTop<scrollTop+clientTop){ //滚动到最后一张图片顶部时
return true; //可以开始加载图片
}
else{
return false;
}
}

以上代码实现了滚动监测,而其中出现当loadimg()方法则是监测是否到达触发点可以加载,
而fixImg()方法则是将需要插入当图片按照瀑布流规则去进行插入,至此,瀑布流效果基本实现。

接下来贴出全部代码,你可以复制到txt文件,然后将其后缀名改为html,使用浏览器打开即可
看道效果。

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
    <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>瀑布流</title>
</head>
<style>
*{
margin: 0;
padding:0;
}

.box{
padding: 5px;
float: left;
}

.img {

}

#container{
position: relative;
}

.appendClass{
width: 250px;
height: 150px;
background-color: #f8e530;
}

div{
font-family: 宋体;
font-size: x-large;
color: #0D3349;
text-align: center;
}

</style>

<body>
<div id="container">
<div class="box">
<div class="img" style="width: 250px;
height: 150px;
background-color: #00AA88;">

爱我你怕了吗</div>
</div>
<div class="box">
<div class="img" style=" width: 250px;
height: 300px;
background-color: #00CCFF;">
来啊,打我啊</div>

</div>
<div class="box">
<div class="img" style="width: 250px;
height: 500px;
background-color: #ff6cf1;">
大傻比</div>

</div>
<div class="box">
<div class="img" style=" width: 250px;
height:80px;
background-color: #b2dba1;">
大坏蛋</div>

</div>
<div class="box">
<div class="img" style="width: 250px;
height: 200px;
background-color: #f5cef4;">
死基佬</div>

</div>
<div class="box">
<div class="img" style=" width: 250px;
height: 300px;
background-color: #00CCFF;">
no can no BB</div>

</div>
<div class="box">
<div class="img" style="width: 250px;
height: 500px;
background-color: #ff6cf1;">
进逼</div>

</div>
<div class="box">
<div class="img1" style="width: 250px;
height: 150px;
background-color: #00AA88;">
你行你上啊</div>

</div>
<div class="box">
<div class="img" style=" width: 250px;
height:80px;
background-color: #b2dba1;">
因垂死听</div>

</div>
<div class="box">
<div class="img" style="width: 250px;
height: 200px;
background-color: #f5cef4;">
阿哈哈哈</div>

</div>
<div class="box">
<div class="img" style="width: 250px;
height: 150px;
background-color: #00AA88;">
活活慌慌呼呼</div>

</div>
</div>
</body>
<script>
window.onload=function(){
fixImg('container','box');

var imgStyleHeight=[150,300,80,200,500];
var imgStyleColor=['#00AA88','#00CCFF','#b2dba1','#f5cef4','#ff6cf1'];
var imgStyleHtml=['you jump,i jump','岁月静好,妙香暖心','似乎很有趣','外星生物入侵啦'];
window.onscroll=function(){ //监测滚动条位置
var oParent=document.getElementById('container');
if(loadImg()){
for(var i=0;i<imgStyleHeight.length;i++){
var oDiv1=document.createElement('div');
oDiv1.className='box';
oParent.appendChild(oDiv1);
var oDiv2=document.createElement('div');
oDiv2.className='img';
// oDiv2.className='appendClass';
oDiv2.style.cssText="width:250px;height:"+imgStyleHeight[i]+"px;background-color:"+imgStyleColor[i]+";";
oDiv2.innerHTML=imgStyleHtml[i];
oDiv1.appendChild(oDiv2);
}
fixImg('container','box');
}
}
};
function loadImg (){ //滚动后图片的加载
var oParent=document.getElementById('container');
var aImg=getChildren(oParent,'box'); //获得已经插入的图片
var lastImgTop=aImg[aImg.length-1].offsetTop; //最后一张距离上方的高度
var scrollTop=document.documentElement.scrollTop||document.body.scrollTop;//滚动高度
var clientTop=document.documentElement.clientHeight||document.body.clientHeight; //当前窗口高度
// console.log(lastImgTop+'<'+scrollTop+'+'+clientTop);
if(lastImgTop<scrollTop+clientTop){ //滚动到最后一张图片顶部时
return true; //可以开始加载图片
}
else{
return false;
}
}
function fixImg(parent,childName){ //使得每一行有固定个数图片,不随窗口大小而改变
var oParent=document.getElementById(parent);
var imgArr=getChildren(oParent,childName);
// console.log(imgArr);
var imgWidth=imgArr[0].offsetWidth;
// console.log(imgWidth);
var cols=Math.floor(document.documentElement.clientWidth/imgWidth); //计算图片列数
// console.log(cols);
//根据图片修改外层div为某固定宽度
oParent.style.cssText="width:"+cols*imgWidth+"px;margin:0 auto;";

//开始把图片按照瀑布流规则插入
var imgHeight=[];
for(var i=0;i<imgArr.length;i++){
if(i<cols){ //图片已经显示
imgHeight[i]=imgArr[i].offsetHeight; //获得当前一行所有图片高度
}
else{
var minHeight=Math.min.apply(null,imgHeight); //找到最小高度
var minIndex=getLocation(imgHeight,minHeight);
imgArr[i].style.position='absolute';
imgArr[i].style.top=minHeight+'px'; //插入在最小高度图片下面
imgArr[i].style.left=imgArr[minIndex].offsetLeft+'px';
//插入图片的那部分高度改变
imgHeight[minIndex]=imgHeight[minIndex]+imgArr[i].offsetHeight;
}
}
}

function getChildren(oParent,childName){ //获得图片个数
var imgArr=[];
var content=oParent.getElementsByTagName('*');
for(var i=0;i<content.length;i++){
if(content[i].className==childName){ //找到指定类名对元素
imgArr.push(content[i]);
}
}
return imgArr; //返回存放所有图片对数组
}

function getLocation(aparent,oMinHeight){ //找到最小高度图片在数组对中对位置
for(var i=0;i<aparent.length;i++){
// console.log('imgHeight:'+aparent+' oMinHeight:'+oMinHeight);
if(aparent[i]==oMinHeight){
// console.log('i: '+i);
return i;
}
else{
console.log('not find');
}
}
}
</script>

</html>

总结

完成这么一个效果的过程中也确实暴露出了一些问题:
比如:像这样的json是没有length属性的

1
var json2={"name":"txt1","name2":"txt2"};

想要遍历只能这样:

1
2
3
    for(var js2 in json2){
alert( js2+"="+json2[js2]);
}

不过后来改用数组了。
还有一些因为手误而造成的bug也浪费不少时间。

不过能看到最后效果顺利实现还是很开心的。O(∩_∩)O
如果你有什么意见或者建议,欢迎在下方留言,我会及时回复。