之前写过一篇用js实现瀑布流效果的文章,可以称为V1.0版本,有兴趣的可以看看:html5实现瀑布流效果。今天既然跟大家分享的是2.0版本,当前是为了解决1.0版本中存在的bug。
1.0版本中实现两列瀑布流的基本思路就是父级元素采用position:relative;相对定位的方式,子元素采用position:absolute;绝对定位的方式,因为子元素可能带有图片,也可能不带图片。这又需要分情况处理,带图片的,由于图片加载速度较慢,需要使用img标签的load事件判断当前子元素的图片已经加载完成后,才能加载下一个子元素,否则子元素会相互重叠在一起。子元素不带图片就没后面那么多的麻烦事,直接使用for循环将一页子元素全部顺序加载完就完事了。效果图如下。
子元素带图片的:
子元素不带图片的:
使用上面所说的思路,来做上面两个单页面项目,没有暴露出明显问题。但是我在另一个心愿漂流瓶项目中,这个思路就受到了挑战,暴露出了一些新的问题。
先上图:
如上图所示,这个页面底部工具栏有3个按钮,将主页分成了心愿区、帮助区和我的3个区域,这3个区域都需要使用瀑布流排布列表项。这样的话,就会暴露出1.0版本瀑布流排布方法的几个bug。
1.第一个bug:先看下面的代码
/**
* 添加心愿列表,瀑布流效果,支持有图片显示和无图片显示
* @param playerList 元素列表
* @param index 列表项页内序号
* @param boxHeightArr 瀑布流左右两列的总高度数组
* @param currentPage 当前所在页
* @param parent 加载元素的父级元素
*/
function addWish(playerList, index, boxHeightArr, currentPage, parent){
var template = "";
var html = "";
var childrenBoxIndex = $(parent).find(".listBox").length - (currentPage - 1) * 10;
if (childrenBoxIndex < playerList.length){
template = '<div class="listBox">';
template += ' <div class="innerBox1">';
template += ' <div class="innerBox2">';
template += ' <a href="/wishDriftBottle/html5/detailedWish.html?id={id}">'; // 在detailedWish.html通过id获取心愿信息
template += ' <div class="idAndName">';
template += ' <img class="officialPic officialPic{wishNo}" style="display: none;" src="/wishDriftBottle/imgs/sourceImgs/official.png">';
template += ' <span class="idSpan">{wishNo}号 {fullname}</span>';
template += ' <img class="status publishing{wishNo}" style="display: none;" src="/wishDriftBottle/imgs/sourceImgs/publishing.png">';
template += ' <img class="status finished{wishNo}" style="display: none;" src="/wishDriftBottle/imgs/sourceImgs/finished.png">';
template += ' </div>';
template += ' <div class="imgAndContent imgAndContent{wishNo}"> </div>';
template += ' <div class="vote">';
template += ' <span class="helpText">已获得{votesNum}人帮助</span>';
template += ' </div>';
template += ' </a>';
template += ' </div>';
template += ' </div>';
template += '</div>';
var wishNo = parseInt(playerList[index].wishNo);
playerList[index].wishNo = wishNo;
html = template.formatString(playerList[index]);
$(parent).append(html);
boxLocation(boxHeightArr, (currentPage - 1)*10 + index, parent); // 计算当前列表元素的坐标(绝对定位)
$(parent)[0].style.height = Math.max.apply(null, boxHeightArr) + "px"; // 为了列表元素和加载提示不重合,需要实时更新players的高度
var wish = playerList[index];
var createType = wish.createType;
if (createType == 1){
$(".officialPic" + wishNo).show();
}
var status = wish.status;
if (status == 1){
$(".publishing" + wishNo).show();
}
else if (status == 2){
$(".finished" + wishNo).show();
}
var wishImgUrls = wish.urls;
if (wishImgUrls != null){ // 当前心愿有图片,显示图片
template = '<img class="wishImg wishImg{wishNo}">'; // 将img元素插入imgAndContent
template += '<div class="imgTitle">{title}</div>';
html = template.formatString(playerList[index]);
$(".imgAndContent" + wishNo).html(html);
var wishImgUrl0 = wishImgUrls[0];
var url = wishImgUrl0.url;
var wishImgName = ".wishImg" + wishNo;
$(wishImgName).attr('src', imagesPrefix + url + "?imageView2/2/w/300");
$(wishImgName).load(function(){
refreshBoxHeightArr(boxHeightArr, (currentPage - 1)*10 + index, parent); // 当前图片加载完后,更新boxHeightArr[]
$(parent)[0].style.height = Math.max.apply(null, boxHeightArr) + "px"; // 为了列表元素和加载提示不重合,需要实时更新players的高度
return addWish(playerList, ++index, boxHeightArr, currentPage, parent); // 自调用,加载完一张图片后,再加载下一张图片,防止图片重叠
});
}
else{ // 当前心愿无图片,显示心愿详情
template = '<span class="content content{wishNo}"></span>'; // 将span(心愿内容)插入imgAndContent
html = template.formatString(playerList[index]);
$(".imgAndContent" + wishNo).html(html);
$(".content" + wishNo).text(wish.title);
refreshBoxHeightArr(boxHeightArr, (currentPage - 1)*10 + index, parent);
$(parent)[0].style.height = Math.max.apply(null, boxHeightArr) + "px"; // 为了列表元素和加载提示不重合,需要实时更新players的高度
return addWish(playerList, ++index, boxHeightArr, currentPage, parent); // 自调用,加载完一张图片后,再加载下一张图片,防止图片重叠
}
}
return;
}
这是加载心愿列表的函数,在实际开发项目的过程中,我发现在页面“我的”一栏下,会出现列表项加载完后,有几个列表项被重复加载的问题,这个问题困扰了我好久,最终经过调试分析发现是$(wishImgName).load(function(){});捣的鬼,因为图片加载较慢,addWish()函数执行完后,index参数已经被释放,所以load事件触发时,取到的列表项序号index就是个错误的,导致重复加载。
解决办法:load事件中,要根据实际DOM的数量计算当前列表项的index序号。
2.第二个bug
按照1.0中的思路,大部分情况下瀑布流加载都是没有问题的,但是偶尔还是有瀑布流排布出错的问题,这个让我很苦恼。经过分析,毕竟计算每个子元素绝对位置的方式不是太稳定,因为每个子元素都需要计算相对父级元素的位置,代码如下:
ccontent[index].style.position = "absolute";
ccontent[index].style.left = ccontent[minIndex].offsetLeft + "px";
ccontent[index].style.top = boxHeightArr[minIndex] + "px";
而且后面的子元素的位置计算都依赖于之前所有已经加载的子元素,一旦有一个子元素的位置计算错误,会导致后面的所有子元素位置全部错乱。一句话总结:这样方法稳定性太差。
所以,需要使用全新的方法来解决这个bug,如果列表项占满一行,根本不用担心列表项重叠的问题,因为div使用的是浮动布局,按照这种思路,完全可以把页面平分成两栏,根据两栏子元素的总高度,决定把下一子元素放在左边或者右边,这样就完美解决了这个bug。
最终,经过优化重构过的代码如下:
/**
* 添加心愿列表,瀑布流效果,支持有图片显示和无图片显示
* @param playerList 元素列表
* @param pageIndex 元素页内序号
* @param boxHeightArr 瀑布流左右两列的总高度数组
* @param currentPage 当前所在页
* @param parent 加载元素的父级元素
*/
function addWish(playerList, pageIndex, boxHeightArr, currentPage, parent){
var template, minheight, minIndex, wish, wishNo, wishImgUrls;
template = "";
if (pageIndex < playerList.length){
wish = playerList[pageIndex];
wishNo = parseInt(wish.wishNo);
playerList[pageIndex].wishNo = wishNo;
wishImgUrls = wish.urls;
template = '<div class="listBox">';
template += ' <div class="innerBox1">';
template += ' <div class="innerBox2">';
template += ' <a href="../html5/detailedWish.html?id='+ wish.id +'">'; // 在detailedWish.html通过id获取心愿信息
template += ' <div class="idAndName">';
if (wish.createType === 1) {
template += ' <img class="officialPic" src="../imgs/sourceImgs/official.png">';
}
template += ' <span class="idSpan" id="idSpan_'+ wishNo +'">'+ wishNo +'号 '+ wish.fullname +'</span>';
if (wish.status === 1) {
template += ' <img class="status" src="../imgs/sourceImgs/publishing.png">';
}
else {
template += ' <img class="status" src="../imgs/sourceImgs/finished.png">';
}
template += ' </div>';
template += ' <div class="imgAndContent">';
if (wishImgUrls != null) {
template += ' <img class="wishImg" id="wishImg_'+ wishNo +'">';
template += ' <div class="imgTitle">'+ wish.title +'</div>';
}
else {
template += ' <span class="content">'+ wish.title +'</span>';
}
template += ' </div>';
template += ' <div class="vote">';
template += ' <span class="helpText">已获得'+ wish.votesNum +'人帮助</span>';
template += ' </div>';
template += ' </a>';
template += ' </div>';
template += ' </div>';
template += '</div>';
minheight = Math.min.apply(null, boxHeightArr);
minIndex = getminheightLocation(boxHeightArr, minheight);
if ($(parent).find("#idSpan_" + wishNo).length === 0) { // 使用page取列表项时,服务端可能返回重复的列表项,需剔除
$(parent).find(".lists_" + minIndex).append(template);
}
if (wishImgUrls != null){ // 当前心愿有图片,显示图片
$(parent).find("#wishImg_" + wishNo).attr("src", imagesPrefix + wishImgUrls[0].url + "?imageView2/2/w/300");
$(parent).find("#wishImg_" + wishNo).load(function(){ // 主页中可能有多个class为wishImgName的img,需指定父级元素,保证唯一性
var allIndex = $(parent).find(".listBox").length - 1;
var pageIndexTmp = allIndex - (currentPage -1)*10;
refreshBoxHeightArr(boxHeightArr, allIndex, parent); // 由于图片加载速度较慢,局部变量pageIndex可能被释放,此处只能使用重新计算的元素总排序allIndex
return addWish(playerList, ++pageIndexTmp, boxHeightArr, currentPage, parent); // 自调用,加载完一张图片后,再加载下一张图片,防止图片重叠
});
}
else{ // 当前心愿无图片,显示心愿详情
refreshBoxHeightArr(boxHeightArr, (currentPage - 1)*10 + pageIndex, parent);
return addWish(playerList, ++pageIndex, boxHeightArr, currentPage, parent); // 自调用,加载完一张图片后,再加载下一张图片,防止图片重叠
}
}
else {
if ($(parent).attr("class") === "all-wish-lists") {
allWishIsLoading = true; // 图片load事件会导致一页列表未加载完就退出addWish(),allWishIsLoading变量需要置于此处
}
else if ($(parent).attr("class") === "myPublishWishList") {
myPublishWishIsLoading = true;
}
else if ($(parent).attr("class") === "mySponsorHelpList"){
mySponsorHelpIsLoading = true;
}
else {
findIsLoading = true;
}
return;
}
}