您的位置:首页 > 其它

关于瀑布流的若干想法

2015-11-02 16:39 344 查看
瀑布流在目前比较流行;我们项目中也用到了;今天终于有时间分享一下自己的一些想法
总的来讲,目前瀑布流的实现方式大概流行的有3种。下面一一解释其实现原理。优劣性。

1.多列浮动。
实现原理大概如下:
一个父容器,内部分为若干列,各列固定宽度,并且左浮动;
每一列为一个单独个体,滚动时,每列中一次追加新的数据块;
更多数据加载时,需要分别插入到不同的列上;
示意图如下:



优点:
1.利用浏览器的流布局,实现成本低,布局简单;不会出现数据块之间重叠的现象(即使数据块中包含了图片)
2.不用考虑数据块中数据,图片的等状态。

当然这种布局也是有缺点:
1.列数固定,扩展不易,当浏览器窗口大小变化时,只能固定的x列,如果要添加一列,很难调整数据块的排列;
当然还有人说指出另外一个问题:滚动加载数据时,需要指定插入到第几列中,不方便。对此我不以为然,因为基于绝对定位的方式,其实在决定数据块定位在那一列时,也需要经过计算等。

刚才在网上看到一个多列浮动瀑布流更加精巧的博客:基于多栏列表的瀑布流布局
效果demo:点这里
相比于上面,博客作者考虑到了不尽考虑到了浏览器纵向的数据块加载;更考虑到了横向(即浏览器宽度变化时)

巧妙之处在于:
在页面上准备id为waterFallDetect空span标签,这个标签作用有两个:
一是实现两端对齐效果,二是用来检测瀑布流布局是否需要刷新。

检测原理如下:
该span标签宽度与一个列表宽度一致,当浏览器宽度变小的时候,如果小到一定程度,显然,浏览器最右边的列表就会跑到下一行,把空span挤到后面去,空span发生较大的水平位移,显然,可以通知脚本,布局需要刷新;当浏览器宽度变大的时候,
如果变大的尺寸超过一列的宽度,显然,这个空span灰跑到第一行去,同样是发生较大的水平位移,因此,又可以通知脚本刷新瀑布流布局了。这个方法的好处是几乎没有计算就可以一点不差地知道何时瀑布流布局需要刷新。
这显然要比设置resize定时器+位置尺寸计算要简单高性能地多。

2.利用CSS3特性
由 chrome/ff 浏览器直接渲染出来,可以指定容器的列个数,列间距,列中间边框,列宽度来实现;
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
#container {
background: #DBDBDB;
height: 500px;
width:700px;
-webkit-column-gap: 10px;
-webkit-column-count: 3;
-webkit-column-width: 210px;
/*-webkit-column-gap: 10px;
-webkit-column-rule: 5px solid #333;
-webkit-column-width: 210px;*/
-moz-column-count: 5;
/*-moz-column-gap: 20px;
-moz-column-rule: 5px solid #333;
-moz-column-width: 210px;*/
column-count: 5;
/*column-gap: 10px;
column-rule: 5px solid #333;
column-width: 210px;*/
}
.item{display:inline-block;height:100px;  width: 150px;
background: red;  margin: 5px 0px;}
</style>
<title></title>
</head>
<body>
<div id="container">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
<div class="item">7</div>
<div class="item">8</div>
<div class="item">9</div>
<div class="item">10</div>
<div class="item">11</div>
<div class="item">12</div>
<div class="item">13</div>
<div class="item">14</div>
<div class="item">15</div>
<div class="item">16</div>
<div class="item">17</div>
<div class="item">18</div>
<div class="item">19</div>
<div class="item">20</div>
<div class="item">21</div>
<div class="item">22</div>
<div class="item">23</div>
<div class="item">24</div>
<div class="item">25</div>
<div class="item">26</div>
<div class="item">27</div>
<div class="item">28</div>
<div class="item">29</div>
<div class="item">30</div>
<div class="item">31</div>
</div>
</body>
</html>
简单测试了一下,很容易实现多列布局


其中需要注意的是:
column-count 为列数;
column-gap 为每列间隔距离;
column-rule 为间隔边线大小;
column-width 为每列宽度;
当只设置 column-width 时,浏览器窗口小于一列宽度时,列中内容自动隐藏;
当只设置 column-count 时,平均计算每列宽度,列内内容超出则隐藏;
都设了 column-count 和column-width,浏览器会根据 count 计算宽度和 width 比较,
取大的那个值作为每列宽度,然后当窗口缩小时,width 的值为每列最小宽度。
这边其实很简单,简易自己尝试下,详细可参考 https://developer.mozilla.org/en/CSS3_Columns 中的说明。
优点:
直接 CSS 定义,最方便了;
扩展方便,直接往容器里添加内容即可。
缺点:
只有高级浏览器中才能使用;

3.绝对定位。这是目前被采用最多;其实现原理其实比较简单;但是计算步骤稍显繁多。
示意图如下:



每一次遍历找到高度最短的一列,并计算好位置,追加新的数据块
目前我们项目中使用的瀑布流加载方式也是这样的。
下面将直接上一个简单的方法,然后具体解释整个实现的方式。

function waterfall(parent, childrenList, callback) {
var hArray = [];//存储每一列高度
var itemWidth = childrenList.eq(0).outerWidth();//每一项的宽度
var cols = Math.floor(parent.width() / itemWidth);//列数
for (var i = 0; i < childrenList.length; i++) {
var tempItem = childrenList.eq(i);
var itemHeight = tempItem.outerHeight();//获取每个元素高度
if (i < cols) {
hArray.push(itemHeight);
tempItem.attr("cols",(i%cols));
} else {
//获取最小高度,及所在列;将新item定位在改列最下面
var minH = Math.min.apply(null, hArray);
var minHIndex = $.inArray(minH,hArray);
/*console.log("第" + i + "item的高度:" + tempItem.outerHeight());
console.log("第" + i + "item的Img高度:"+ tempItem.find("img").height())
setTimeout(function () {
console.log("延迟一秒后,第" + i + "item的高度:" + tempItem.outerHeight());
},100);*/
tempItem.css({ "position": "absolute", "top": minH, "left": itemWidth * minHIndex }).attr("cols",minHIndex);
}
hArray[minHIndex] += itemHeight;//更新改列的高度
}
//console.log("高度数组:" + hArray);
parent.css({'height':Math.max.apply(null, hArray)});//容器高度设为数组最大值
callback && callback(hArray);//回调
}
其实上面的代码只是执行瀑布流加载的代码。注释比较清楚,所以具体实现过程就不多少

那么问题来了,何时执行瀑布流呢?

一般来讲,我们采用的方式是,计算最长的那一列距离dom顶端的距离,然后与浏览器窗口的大小及scrollTop做差值。然后在适当范围内执行数据加载。
我们在这里采用了另一种方式,因为页面有一个footer;当footer进入视野时,加载;代码大概这个样子:
var winHeight = $(window).height();
var scrollTop = $(window).scrollTop();//窗口距页面顶部距离
var footerTop = $(".m-footer").offset().top;//页脚距离页面顶部的距离
var lineH = footerTop - (scrollTop + winHeight);
当然具体怎么计算执行瀑布流,这个不是重点。根据各自的情况而决定,才是王道。

另外一个比较痛苦的就是当数据块包含图片时,问题就来了;可以看到上面我注释掉了很多console;诸如:
console.log("第" + i + "item的高度:" + tempItem.outerHeight());
在实际使用中,我们发现,瀑布流出现了重叠的现象(这也是绝对定位方式实现瀑布流一大bug)原因嘛,比较明显,执行瀑布流时,图片还没有加载渲染完毕;导致数据块的高度计算不准确;console打印一下;果真,大部分图片的高度都为0;所以计算出来位置就不对了。

为了解决这个问题,我使用了一个定时器,定时去检测图片的状态
但是经过测试,问题又来了;
imgObj.complete属性在chrome下,不管图片加载成功失败都返回true;
IE8,图片加载失败imgObj.readyState返回uninitailized ;imgObj.complete返回false;
加载成功imgObj.complete=true;imgObj.readyState=complete

图片加载失败的处理方式,不是本篇博客的重点;我直接用alt="图片加载失败"替换。

言归正传:所以我们要等所有图片加载完成(不管成功还是失败)后,再执行瀑布流
故检测图片状态并执行瀑布流的代码如下:
var htmlStr = config.AssembleItem && config.AssembleItem(data);
config.box_list.append(htmlStr);
var imgArr = config.box_list.children().find("img");
var imgLength = imgArr.length;
var timer = setInterval(function(){
var count = 0;
for (var i = 0; i < imgLength ; i++) {
if(imgArr[i].complete || "uninitailized"=== imgArr[i].readyState) {
count+=1;
//console.log("count:" + count);
}
}
if(imgLength === count){
clearInterval(timer);
waterfall(config.box_list, config.box_list.children(), config.callback);
config.loadEnable = true;
config.curPageNum++;
config.loading.fadeOut(500);
}
}, 100);
另外一种解决上述问题的方案就是后台能够告诉你图片的高度,那样你就不用花心思检测图片状态了;这也是我们后期瀑布流优化的方向。

当然还要注意的一点,由于滚动事件触发的条件(距离,此时)在不同浏览器下不尽相同(之前写过一篇关于滚动事件在不同浏览器下的差异性文章:点这里可以看到),如果前一次瀑布流执行没有完毕,下次瀑布流是不能执行的故加了一个标记位:config.loadEnable = true;

好了,就说到这里。欢迎拍砖。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: