之前写过一篇用js实现瀑布流效果的文章,可以称为V1.0版本,有兴趣的可以看看:html5实现瀑布流效果。今天既然跟大家分享的是2.0版本,当前是为了解决1.0版本中存在的bug。

       1.0版本中实现两列瀑布流的基本思路就是父级元素采用position:relative;相对定位的方式,子元素采用position:absolute;绝对定位的方式,因为子元素可能带有图片,也可能不带图片。这又需要分情况处理,带图片的,由于图片加载速度较慢,需要使用img标签的load事件判断当前子元素的图片已经加载完成后,才能加载下一个子元素,否则子元素会相互重叠在一起。子元素不带图片就没后面那么多的麻烦事,直接使用for循环将一页子元素全部顺序加载完就完事了。效果图如下。

子元素带图片的:

jquery marsy 瀑布流 js实现瀑布流效果_瀑布流


子元素不带图片的:

jquery marsy 瀑布流 js实现瀑布流效果_瀑布流_02


       使用上面所说的思路,来做上面两个单页面项目,没有暴露出明显问题。但是我在另一个心愿漂流瓶项目中,这个思路就受到了挑战,暴露出了一些新的问题。

       先上图:

jquery marsy 瀑布流 js实现瀑布流效果_jquery marsy 瀑布流_03


       如上图所示,这个页面底部工具栏有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;
    }

}