一、开篇无关紧要的话
今天四处闲逛,看到迅雷UEDxwei兄写了篇名为“浅谈个人在瀑布流网页的实现中遇到的问题和解决方法(http://cued.xunlei.com/log031)”的文章,我两只沉沉的萝卜眼顿时放出无数闪亮的小星星。
倒不是文章本身,而是可以用来制作demo的图片资源啊,啊咔咔(得瑟中……)!!
因此,本文即将展示的demo中的图片都有迅雷UED提供,这里先郑重感谢。
二、稍稍要紧的话
跟风,尤其受pinterest的煽风点火,瀑布流现在不少人关注。我正好最近比较闲,加上有人曾在我站点提出希望我介绍点瀑布流的东西,所以,今儿个也随下大流。
pinterest以及上面迅雷UED xwei的瀑布流demo(至少在FireFox下还是有致命的显示bug的)都是采用的绝对定位实现的,有相对复杂的位置计算。
我一向不喜欢吃别人嚼过的米饭,于是尝试使用另外的原理实现。我是个流体布局控,对绝对定位啊、浮动啊什么的一向没什么好感,于是,这里要介绍的就是基于多栏列表流体布局实现的瀑布流布局效果。
大致结构、布局见下面的手绘图:
没有复杂的位置计算,不需要知道里面元素的高度以及宽度,且易理解,关键是具体实现~~
三、高潮来了:demo展示
您可以狠狠地点击这里:基于多栏列表瀑布流布局demo
欢迎各种滚动,缩放等测试。低版本IE浏览器也是兼容滴。问题嘛也是有滴,就是滚动到一定位置再F5刷新的时候,部分加载的内容有丢失,需要重新滚动加载。这个嘛,我个人觉得小小demo,没必要折腾啦(实际上要实现也比较容易,改动如下:每次滚动不是append一个节点,而是连续回调直到加载到屏幕下方。不懂什么意思?花点功夫看看JS实现原理就会明白了)~~
四、说说原理
第一次进入的时候,根据浏览器宽度以及每列宽度计算出列表个数,然后不管三七二十一,每列先加载个5张图片再说。
当滚动的时候,对每一列的底部位置做检测,如果在屏幕中或屏幕上方,则立即append一个新图片(注意:为了简化代码,提高性能,同时便于演示等,这里只append了一个)。因为,滚动时连续的,因此,我们实际看到的效果是图片不断load出来。
当浏览器宽度改变的时候,页面上有个id
为waterFallDetect
空span
标签,这个标签作用有两个:一是实现两端对齐效果,二是用来检测瀑布流布局是否需要刷新。
检测原理如下:
该span
标签宽度与一个列表宽度一致,当浏览器宽度变小的时候,如果小到一定程度,显然,浏览器最右边的列表就会跑到下一行,把空span
挤到后面去,空span
发生较大的水平位移,显然,可以通知脚本,布局需要刷新;当浏览器宽度变大的时候,如果变大的尺寸超过一列的宽度,显然,这个空span
灰跑到第一行去,同样是发生较大的水平位移,因此,又可以通知脚本刷新瀑布流布局了。
这个方法的好处是几乎没有计算就可以一点不差地知道何时瀑布流布局需要刷新。这显然要比设置resize定时器+位置尺寸计算要简单高性能地多。
滚动时的页面刷新是基于HTML字符串的处理,而不是更改每个DOM元素的位置(这是绝对定位实现的处理),因此,这里的效率显然更高。
五、总结:基于多栏列表流体布局瀑布流效果优点
- 简单:最大限度利用了浏览器的流体特性进行布局,省去了很多计算的麻烦;新人更易懂和上手
- 更好的性能:这个体现在多处,如浏览器宽度改变,瀑布流刷新时候的效率等
- 无需知晓尺寸:如果是要绝对定位实现瀑布流,必须知道每个小模块的高度以及宽度(否则无法定位),而基于列表的布局则无需知道高宽
无聊时候的折腾,有不足与不准确之处欢迎指正。一些实现的具体细节等也是非常欢迎提问交流的。
原创文章,转载请注明来自张鑫旭-鑫空间-鑫生活[http://www.zhangxinxu.com]
本文地址:http://www.zhangxinxu.com/wordpress/?p=2308
[上面Demo代碼]
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>瀑布流布局测试</title>
<style>
body
{
background-color: #eee;
font-size: 84%;
text-align: justify;
}
.column
{
display: inline-block;
vertical-align: top;
}
.pic_a
{
display: block;
padding: 5px;
margin-bottom: 10px;
border: 1px solid #ccc;
background-color: #fff;
text-decoration: none;
}
.pic_a img
{
display: block;
margin: 0 auto 5px;
border: 0;
vertical-align: bottom;
}
.pic_a strong
{
color: #333;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
var waterFall = {
container: document.getElementById("container"),
columnNumber: 1,
columnWidth: 210,
// P_001.jpg ~ P_160.jpg
rootImage: "http://cued.xunlei.com/demos/publ/img/",
indexImage: 0,
scrollTop: document.documentElement.scrollTop || document.body.scrollTop,
detectLeft: 0,
loadFinish: false,
// 返回固定格式的图片名
getIndex: function () {
var index = this.indexImage;
if (index < 10) {
index = "00" + index;
} else if (index < 100) {
index = "0" + index;
}
return index;
},
// 是否滚动载入的检测
appendDetect: function () {
var start = 0;
for (start; start < this.columnNumber; start++) {
var eleColumn = document.getElementById("waterFallColumn_" + start);
if (eleColumn && !this.loadFinish) {
if (eleColumn.offsetTop + eleColumn.clientHeight < this.scrollTop + (window.innerHeight || document.documentElement.clientHeight)) {
this.append(eleColumn);
}
}
}
return this;
},
// 滚动载入
append: function (column) {
this.indexImage += 1;
var html = '', index = this.getIndex(), imgUrl = this.rootImage + "P_" + index + ".jpg";
// 图片尺寸
var aEle = document.createElement("a");
aEle.href = "###";
aEle.className = "pic_a";
aEle.innerHTML = '<img src="' + imgUrl + '" /><strong>' + index + '</strong>';
column.appendChild(aEle);
if (index >= 160) {
//alert("图片加载光光了!");
this.loadFinish = true;
}
return this;
},
// 页面加载初始创建
create: function () {
//根据宽度计算放多少列(栏)
this.columnNumber = Math.floor(document.body.clientWidth / this.columnWidth);
var start = 0, htmlColumn = '', self = this;
for (start; start < this.columnNumber; start += 1) {
htmlColumn = htmlColumn + '<span id="waterFallColumn_' + start + '" class="column" style="width:' + this.columnWidth + 'px;">' +
function () {
var html = '', i = 0;
for (i = 0; i < 5; i += 1) {
self.indexImage = start + self.columnNumber * i;
var index = self.getIndex();
html = html + '<a href="###" class="pic_a"><img src="' + self.rootImage + "P_" + index + '.jpg" /><strong>' + index + '</strong></a>';
}
return html;
}() +
'</span> ';
}
htmlColumn += '<span id="waterFallDetect" class="column" style="width:' + this.columnWidth + 'px;"></span>';
this.container.innerHTML = htmlColumn;
this.detectLeft = document.getElementById("waterFallDetect").offsetLeft;
return this;
},
refresh: function () {
var arrHtml = [], arrTemp = [], htmlAll = '', start = 0, maxLength = 0;
for (start; start < this.columnNumber; start += 1) {
var arrColumn = document.getElementById("waterFallColumn_" + start).innerHTML.match(/<a(?:.|\n|\r|\s)*?a>/gi);
if (arrColumn) {
maxLength = Math.max(maxLength, arrColumn.length);
// arrTemp是一个二维数组
arrTemp.push(arrColumn);
}
}
// 需要重新排序
var lengthStart, arrStart;
for (lengthStart = 0; lengthStart < maxLength; lengthStart++) {
for (arrStart = 0; arrStart < this.columnNumber; arrStart++) {
if (arrTemp[arrStart][lengthStart]) {
arrHtml.push(arrTemp[arrStart][lengthStart]);
}
}
}
if (arrHtml && arrHtml.length !== 0) {
// 新栏个数
this.columnNumber = Math.floor(document.body.clientWidth / this.columnWidth);
// 计算每列的行数
// 向下取整
var line = Math.floor(arrHtml.length / this.columnNumber);
// 重新组装HTML
var newStart = 0, htmlColumn = '', self = this;
for (newStart; newStart < this.columnNumber; newStart += 1) {
htmlColumn = htmlColumn + '<span id="waterFallColumn_' + newStart + '" class="column" style="width:' + this.columnWidth + 'px;">' +
function () {
var html = '', i = 0;
for (i = 0; i < line; i += 1) {
html += arrHtml[newStart + self.columnNumber * i];
}
// 是否补足余数
html = html + (arrHtml[newStart + self.columnNumber * line] || '');
return html;
}() +
'</span> ';
}
htmlColumn += '<span id="waterFallDetect" class="column" style="width:' + this.columnWidth + 'px;"></span>';
this.container.innerHTML = htmlColumn;
this.detectLeft = document.getElementById("waterFallDetect").offsetLeft;
// 检测
this.appendDetect();
}
return this;
},
// 滚动加载
scroll: function () {
var self = this;
window.onscroll = function () {
// 为提高性能,滚动前后距离大于100像素再处理
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
if (!this.loadFinish && Math.abs(scrollTop - self.scrollTop) > 100) {
self.scrollTop = scrollTop;
self.appendDetect();
}
};
return this;
},
// 浏览器窗口大小变换
resize: function () {
var self = this;
window.onresize = function () {
var eleDetect = document.getElementById("waterFallDetect"), detectLeft = eleDetect && eleDetect.offsetLeft;
if (detectLeft && Math.abs(detectLeft - self.detectLeft) > 50) {
// 检测标签偏移异常,认为布局要改变
self.refresh();
}
};
return this;
},
init: function () {
if (this.container) {
this.create().scroll().resize();
}
}
};
waterFall.init();
</script>
</body>
</html>