浅谈响应式瀑布流的实现方式

瀑布流主要应用在图片展示页面上。如果有一大批图片需要展示,原始图片尺寸不一致,又希望每张图片都能不剪裁,完整显示,那么就要给图片规定一个宽度,解放它们的高度。利用网页高度不限这个特性,充分利用页面的空间,尽可能的展示多的图片。瀑布流的实现方法挺多,但能做到响应式列数变化,自由布局的并不多。这次自己开发了一个作品集页面,正好研究一下响应式瀑布流的实现方法。

响应式瀑布流布局的思路

响应式瀑布流需要解决的问题:

1. 响应式多列:960px宽以上呈4列,750-960呈3列,400-750呈2列,400宽以下变成1列;
2. 由于需要做响应式,那么每块的宽度就不固定,导致高度更加不可能固定。后端输出的时候,图片(块)的高度本来就是未知的,要用JS实时取到它们的高度,以便调整布局。
3. 异步加载:页面拖到最低端的时候加载更多,这个很常见。

响应式瀑布流实现过程:

1. 先用offsetWidth取瀑布流大容器<ul>的实际宽度totalWidth,JS判断这个宽度可以放几列,但不给每列分配容器,而是用数组column记录每列的高度。给每个图片块分配一个<li>,每个<li>给一个class,4列就是col4,3列的就是col3。在css中给这些col+数字的class定宽度,比如这样:

1
2
3
4
.col1 { width: calc(100% - 20px); }
.col2 { width: calc(50% - 20px); }
.col3 { width: calc(330% - 20px); }
.col4 { width: calc(250% - 20px); }

当然要做好兼容性,不兼容calc的浏览器(IE9-,安卓手机等)就用小一点的百分比代替;

2. 每个<li>的宽度定完后,高度都是auto,在JS里用offsetHeight取高度,用offsetWidth取宽度。然后设置每个<li>的style.top和style.left
style.top的值为数组column中最小的那个元素的值;
style.left的值为column中最小的那个元素的序号×每个<li>的offsetWidth宽度;
每个<li>的位置设置完毕后,重新把添加的高度存入数组column中,并再设置<ul>的高度比column中最大的那个元素再大一些

3. 先加载一定数量的<li>,监听页面滚动,滚动到底部再加载更多数量的,然后重新排列;

4. 监听页面resize事件,如果发生,则执行所有命令重新排列;

5. 给所有的JS执行一个延时,防止浏览器反复resize造成页面卡住;

6. 用CSS3的transition给每个<li>的位置加缓动效果,给<ul>的高度也加缓动效果,这样页面resize的时候会很舒服;

JS代码如下,版面限制,省略addEvent,addClass等几个常用的自定义函数的篇幅:

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
var pinterest_doing = 0; //是否正在排列
var pinterest_current = 0; //当前排列到第几个
var pinterest_done = 0; //是否全部加载完毕
var pinterestObj = document.getElementById("pinterestList"); //设定哪个&lt;ul&gt;容器作为瀑布流的总容器
 
function pinterestInit(obj,add){
	var perBlock = 16;//设定每次加载块数
	var gapWidth = 25;//设定块间距
	var containerPadding = 5;//设定外边距
	var columns = 4;//设定最大列数
 
	pinterest_doing = 1;//开始排列
	obj.style.transition = "height 1s"; //给容器高度变化添加缓动
	var totalWidth=obj.offsetWidth;
	if(totalWidth<=720) { //根据容器宽度判断列数
		columns--; 
		if(totalWidth<=552) {
			columns--;
			if(totalWidth<=312) {
				columns--;
			}
		}
	}
	obj.className="pinterestUl";
	addClass(obj, "col"+columns);
	var singleWidth=totalWidth/columns-gapWidth;
	var column=new Array(); //建立一个存储每个列高度的数组
	for(i=0;i<columns;i++){//set the columns and each top
		if (!column[i]) column[i]=0; //初始化数组内每个高度是0
	}
	function findMaxHeight(){ //查询数组内最高的高度
		var maxHeight=column[0];
		var maxColum=0;
		for(var i=0;i<column.length;i++){
			if(maxHeight<=column[i]){
					maxHeight=column[i];
					maxColum=i;
				}
			}
		return {"maxHeight":maxHeight, "maxColum":maxColum } //输出最高高度的对象
	}
	function findMinHeight(){ //查询数组内最低高度
		var minHeight=column[0];
		var minColum=0;
		for(var i=0;i<column.length;i++){
			if(minHeight>column[i]){
					minHeight=column[i];
					minColum=i;
				}
			}
		return {"minHeight":minHeight, "minColum":minColum } //输出最低高度对象
	}
 
	var totalItem=obj.children.length;
	if(add) {
		pinterest_current+=perBlock; //判断是否需要增加加载量
	}
	for(var num=0; num<totalItem; num++ ){ //这里开始排列每块的位置
		if (num>= Math.max(pinterest_current, perBlock) ) break;
		obj.children[num].style.display="block";
 
		var atColum=findMinHeight().minColum;
		var atHeight=findMinHeight().minHeight;
 
		obj.children[num].style.left =  atColum * (singleWidth + gapWidth) + containerPadding + "px";
		obj.children[num].style.top = gapWidth + atHeight + "px" ;
		column[atColum] += obj.children[num].offsetHeight+gapWidth;
 
	}
	pinterest_current = num ; //记录下排列到第几个块
	if(pinterest_current>=totalItem){//全部加载完毕
		pinterest_done=1;
		document.getElementById("pinterestDone").style.display="block";
	}
 
	obj.style.height= (findMaxHeight().maxHeight+30)+"px";
	setTimeout( function(){ //过半秒再设置pinterest_doing为0,然后才能重新执行pinterestInit,防止浏览器崩溃
		pinterest_doing=0;
	}, 500);
}
 
addEvent(window, "resize", function(){ //页面窗口尺寸变化监听
	setTimeout( function(){
		if(pinterest_doing==0) { //pinterestInit执行的必要条件
			pinterest_doing=1;
			pinterestInit(pinterestObj);
		}
	}, 500);
});
 
addEvent(window, "scroll", function(){ //滚动监听
	if (document.body.scrollHeight-getViewPortSize().y <= getScrollOffsets().y+2){
		if(!pinterest_done){//如果没有全部加载完毕,显示loading图标
			addClass(pinterestObj ,"pinterestUl_loading");
		}else {//如果全部加载完毕,显示已经全部加载完毕的提示语
			document.getElementById("pinterestDone").style.display="block";
		}
		setTimeout( function(){
			if(pinterest_doing==0) {
				pinterest_doing=1;
				pinterestInit(pinterestObj, true );
			}
		}, 500);
	}
});
addDOMLoadEvent( pinterestInit(pinterestObj) ); //页面DOM加载完毕才开始执行瀑布流排序

CSS就不贴了,在线例子见http://blog.brain1981.com/artworks

本站所有文章均为原创,欢迎转载,但请注明文章出处:http://blog.brain1981.com/829.html

关注我们的微信公众号-JennyStudio 本站记录了近几年的工作中遇到的一些技术问题和解决过程,“作品集”还收录了本人的大部分作品展示。除了本博客外,我们的工作室网站 – JennyStudio,内有更多作品回顾和展示。
您也可以扫描左边的二维码,关注我们的微信公众号,在微信上查看我们的案例。