waterfall瀑布流布局+動態渲染

瀑布流典型網站

花瓣網、堆糖

[目錄]

  • 瀑布流布局原理
    • 大體思路
    • 具體思路
  • 插件封裝(5步)
  • 動態渲染
    • 需求分析
    • 渲染第一頁數據
      • 發送請求
      • 渲染頁面
    • 第二頁面的渲染(手動加載)
    • 第二頁面的渲染(滾動加載)
  • 特別需要注意的問題

瀑布流布局原理

大體思路

首先先是頁面布局
特點是:寬度一樣,長度不一樣

瀑布流布局原理圖.png

由此可以知道,這種布局要用到絕對定位的思想來做。
上面的五個正常排列,到了第六個以后就要找最矮的追加了。

如何獲取最矮的一列呢?
第一個最好找,每一個盒子可以獲取它的高度,找最矮的盒子,然后找到最矮盒子的定位。

新追加進去的盒子的定位是:
left:最矮盒子的索引*(盒子的寬度+左右間距)
top: 這個盒子的高度 + 上下間距

放進去之后這一列的高度變化,記錄下來生成新的高度,然后進行下一輪高度的比較。以此類推。

waterful是一個組件,基于jquery的一個組件。

具體思路

瀑布流原理.png

最外邊的左右兩邊是沒有間距的,所以5列的情況下有4個間距。
所以寬度width一定的情況下,間距的寬度space是可以計算出來的:
間距

var space = (wParent - 5 * width) / (col - 1);
// wParent 父盒子的寬度,width是子盒子的寬度,col是列數

第一排的盒子的定位:
top : 0
left : 索引*(width + space)

第二排的盒子的定位:
top : minHeight + space
left : 索引*(width + space)

所以5列的高度要用一個數組表示,可以找到最矮的元素以及其當前的索引。

插件封裝

因為在第一次加載和第n次加載的時候,都要進行瀑布流布局。所以將瀑布流布局的方法進行一個插件進行封裝,可以形成代碼的復用。
首先了解瀑布流的html布局

<!--頁面容器-->
<div class = "container">
      <!--所有item的集合,距離頂部有距離-->
      <div class = "items">
             <!--每一個小塊,包含了圖片和文字-->
              <div class = "item">
                    <img src = "" />
                    <p>hello</p>
              </div>

              <div class = "item">
                    <img src = "" />
                    <p>hello</p>
              </div>
      </div>
</div>
<div class = "btn">正在加載...</div>

下面就來封裝一個jquery的插件

第一步

將jquery中的全局變量轉化為局部變量。

  1. 防止全局污染,提高性能
  2. 形成一個閉包,閉包里面定義的變量是不會影響外部變量的。
/*自調用  形成一個閉包 */
(function($){
/*如果不加jQuery里面使用的$是全局變量,加了之后使用的就是成員變量*/

})(jQuery);

第二步

jquery.fn.extend(object)
jquery中的fn函數
提供一個第三方方法的一個入口,擴展jquery元素集(使用$可以獲取到的元素) 來提供新的方法(通常用來制作插件)

/*js/jquery-waterfall.js*/
(function($){
     $.fn.waterfall = function(){
            /*this指向的是當前調用這個方法的元素集(元素集是jquery獲取元素是一個偽數組)*/
             console.log(this);

     }
})(jQuery);

第三步

對第一排進行排列

(function($){
    $.fn.waterfall = function(){
    // this指向的是當前調用這個方法的元素集
    // 當前的瀑布流父容器
        var items = $(this);
        //父容器的寬度
        var wParent = items.width();
        //當前的瀑布流子容器
        var child = items.children();
        //獲取子容器的寬度
        var width = child.width();
        //假設排多少列
        var col = 5;
        //計算間距(父元素的寬度減所有盒子的寬度/4)
        var space = (wParent - col * width) / (col - 1);

        //記錄每列高度的數組
        var colHeightArr = [];

        //遍歷每一個子元素
        $.each(child,function(i,item){
            var $item = $(item);
            var height = $item.height();

            //設置定位
            //第一排的元素都是靠頂部的,所以索引從0開始,小于5的時候都是靠頂部的
            if(i < col ){
                $item.css({
                    top: 0,
                    left:i * (width + space)  
                });
               //把高度添加進數組中
                colHeightArr[i] = height;
                //也可以用   colHeightArr.push(height);
            }
            //其他的都要根據最矮的一列進行排列
        });
    }
})(jQuery);

這樣你就看到了效果圖(因為模擬做了13個盒子,所以剩下的疊在了一起)

第一排布局.png

這個時候打印以下高度數組:

colHeightArr.png

可以看到前5個的高度都存到數組中去了。可以判斷出來數組中最小的是289,289對應的數組的索引就是那一列的索引。

第四步

對其余的排進行排列。找最小的追加,然后本列的高度增加。以此類推。
最大的items等于最大的高度。這樣才可以把下面的加載移動到下邊去。

(function($){
    $.fn.waterfall = function(){
    // this指向的是當前調用這個方法的元素集
    // 當前的瀑布流父容器
        var items = $(this);
        //父容器的寬度
        var wParent = items.width();
        //當前的瀑布流子容器
        var child = items.children();
        //獲取子容器的寬度
        var width = child.width();
        //假設排多少列
        var col = 5;
        //計算間距
        var space = (wParent - col * width) / (col - 1);

        //記錄每列高度的數組
        var colHeightArr = [];

        //遍歷每一個子元素
        $.each(child,function(i,item){
            var $item = $(item);
            var height = $item.height();

            //定位

            //第一排的元素都是靠頂部的

            //索引從0開始,小于5的時候都是靠頂部的
            if(i < col ){
                $item.css({
                    top: 0,
                    left:i * (width + space)
                });

                //colHeightArr[i] = height;
                colHeightArr.push(height);
   
             //其他的都要根據最矮的一列進行排列
             }else{    
                //找到最矮的那一列進行排列
                //索引
                var index = 0;
                //假設最小的高度是第一個索引對應的高度
                var minHeight = colHeightArr[index];
                //遍歷數組,找到最小值和最小值對應的索引
                //k是索引,v是值
                $.each(colHeightArr,function(k,v){
                    if(minHeight > v){
                        index = k;
                        minHeight = v;
                    }
                });

                //定位
                $item.css({
                    top:minHeight + space,
                    left:index * (width + space)
                })

                //當前數組中最小的高度進行新的高度的更新
                colHeightArr[index] = minHeight + space + height;
            }
            //console.log(colHeightArr);
        });

        //設置父容器的高度
        var maxHeight = colHeightArr[0];
        $.each(colHeightArr,function(k,v){
            if(maxHeight < v){
                maxHeight = v;
            }
        });
        //給父容器設置最高的高度
        items.height(maxHeight);
    }
})(jQuery);

效果圖:

其余排的布局.png

第五步

html中調用(上面的效果圖都是已經調用過的)

    $(".items").waterfall();

但是如果有圖片的話,這樣調用在網絡比較慢的情況下會出現問題。在圖片沒有加載出來的時候排列,中間圖片加載完畢會造成盒子重疊的效果。
解決辦法:

/*頁面上所有的資源都加載完成后進行布局,否則獲取不到圖片的尺寸撐不開盒子的高度*/
window.onload = function(){
        $(".items").waterfall();
}

//為什么不用jquery的,因為這個是在dom元素下載完畢之后進行加載這個方法,需要等所有的資源加載完之后進行排列
/*   $(function(){
//console.log('dom loaded');
});    
*/

動態渲染

因為數據很多,所以會進行分批次渲染。
原理圖:

數據交互原理圖.png

接口文檔:
接口說明: 瀑布流分頁數據
接口地址:data.php
請求方式:get
接口參數:page 當前是第幾頁
pageSize 當前頁要顯示多少條
返回類型:json
返回數據:
{page:2,items:[{path:"./images/1.jpg",text:'''},...]}
page 下一頁的頁碼(根據頁碼獲取下一頁的數據)
items 返回當前頁的數據
path 圖片地址
text 文字

此時我們要準備好殼子

<div class="container">
    <div class="items">
        <!--TODO 需要渲染數據的地方-->
    </div>
    <div class="btn loading">正在加載中...</div>
</div>

需求分析

  • 加載第一頁的時候
    • 1.加載第一頁的數據 ajax
    • 2.按鈕需要顯示成加載更多
    • 3.加載完成渲染到頁面當中 artTemplate
    • 4.初始化成瀑布流布局 waterfall
  • 加載下一頁的時候
    • 1.加載數據
      • 手動加載:點擊按鈕加載下一頁的數據
      • 自動加載:滾動到底部的時候主動加載下一頁
    • 2.按鈕需要顯示“正在加載中...”不能點擊 防止重復提交
    • 3.加載完成渲染到頁面當中
    • 4.初始化成瀑布流布局
    • 5.按鈕需要顯示成加載更多
  • 沒有更多數據 把按鈕禁用 顯示“沒有更多數據了”

渲染第一頁數據

發送請求

既然加載頁面的時候都會用到加載數據、渲染頁面、初始化瀑布流,就把這三個動能封裝到一個函數中去,先實現第一個功能:

 $(function(){
        //實現動態的瀑布流渲染
      
        //渲染
        var render = function(){
            // 加載數據  渲染頁面  瀑布流布局
            $.ajax({
                type:'get',
                url:'data.php',
                data:{
                    //第一頁
                    page:1,
                    //每頁10條
                    pageSize:10
                },
                dataType:'json',
                success:function(data){
                    console.log(data);
                }
            });
        }

        render();
    });

拿到的數據如圖:

data.png

渲染頁面

準備模板

<script type="text/template" id="template">
    <% for(var i=0 ; i<items.length ; i++){ %>
        <div class="item">
            <img src="<%=items[i].path%>" alt="">
            <p><%=items[i].text%></p>
        </div>
    <% } %>
</script>


<script>
    $(function(){
       
        //獲取需要操作的dom
        var $items = $(".items");
        var $btn = $(".btn");

        //渲染
        var render = function(){
            // 加載數據  渲染頁面  瀑布流布局
            $.ajax({
                type:'get',
                url:'data.php',
                data:{
                    page:1,
                    pageSize:10
                },
                dataType:'json',
                success:function(data){
                    console.log(data);
                    $items.append(template('template',data));
                    //瀑布流布局
                    $items.waterfall();
                    //更改按鈕
                    $btn.removeClass('loading').html('加載更多');
                }
            });
        }

        render();
    });
</script>

第二頁面的渲染(手動加載)

第二頁需要改變的東西:

  1. 添加按鈕的點擊事件,點擊按鈕之后就進行渲染。
  2. 點擊按鈕加載的時候,要給按鈕加鎖,因為不加的話會發送多個ajax請求,判斷按鈕是不是loading狀態,如果是的話就不渲染數據。
  3. render函數中,在進行按鈕狀態改變的時候,用自定義屬性記錄下來下一頁的要獲取的頁數。利用data(),里面傳一個page,把data.page放進去。所以在拿數據的時候,要從按鈕的data中獲取page的值。第一次是空的,所以就設定一個默認值為1
  4. render函數中在數據成功加載之前,按鈕還是loading狀態,所以加一個beforeSend的函數,里面是loading狀態。
  5. render函數中在渲染的時候判斷一下是不是沒有數據了,根據返回的數組中的長度是不是為零來判斷,如果是零的話就顯示沒有更多數據了。
 $(function(){
        //獲取需要操作的dom
        var $items = $(".items");
        var $btn = $(".btn");

        //渲染
        var render = function(){
            // 加載數據  渲染頁面  瀑布流布局
            $.ajax({
                type:'get',
                url:'data.php',
                data:{
                    //取下一頁的頁碼,沒有的話就默認是1
                    page:$btn.data("page")||1,
                    //每頁10條
                    pageSize:10
                },
                beforeSend:function(){
                    $btn.addClass("loading").html('正在加載中...');
                },
                dataType:'json',
                success:function(data){
                    console.log(data);
                    //準備模板
                    //因為是追加所以不能用html,要用append
                            //直接用data的原因是因為data本來就是一個對象,里面有很多的屬性,而不是一個數組,數組的話不能這樣,因為數據只有length一個屬性
                    $items.append(template('template',data));
                    //瀑布流布局
                    $items.waterfall();

                    if(data.items.length){
                        //更改按鈕
                        //data是一個自定義屬性,把數據中傳輸出來的page保存在自定義屬性當中,
                        $btn.data("page",data.page).removeClass('loading').html('加載更多');
                    }else{
                        //沒有更多數據
                        //判斷什么時候就沒有數據了,打開最后的一個對象,里面items的數組的長度為零
                        $btn.addClass("loading").html("沒有更多數據了");
                    }


                }
            });
        }

        //按鈕加載
        $btn.on('click',function(){
            //避免發送多個ajax請求,就進行判斷,如果是loading狀態,就退出,
            if($btn.hasClass("loading")){
                return false;
            }
            render();
        })

        render();
    });

第二頁面的渲染(滾動加載)

說到滾動渲染,就是要我們渲染過的頁面到瀏覽器底部的一定距離就要進行下一次的請求了,這就要進行一個判斷了。
原理圖:

Paste_Image.png

當bottom < 200px的時候進行ajax請求。
其中bottom要怎么計算?
bottom = items的高度 + items距離頂部的距離 - 向上卷曲的高度 - 整個瀏覽器的高度

 $(function(){
        //實現動態的瀑布流渲染
   
        //獲取需要操作的dom
        var $items = $(".items");
        var $btn = $(".btn");

        //渲染
        var render = function(){
            // 加載數據  渲染頁面  瀑布流布局
            $.ajax({
                type:'get',
                url:'data.php',
                data:{
                    page:$btn.data("page")||1,   
                    pageSize:10
                },
                beforeSend:function(){
                    $btn.addClass("loading").html('正在加載中...');
                },
                dataType:'json',
                success:function(data){
                    console.log(data);
                    $items.append(template('template',data));
                    //瀑布流布局
                    $items.waterfall();

                    //判斷數組中有沒有數據
                    if(data.items.length){
                     $btn.data("page",data.page).removeClass("loading").html('加載更多');
                    }else{
                        $btn.addClass("loading").html("沒有更多數據了");
                    }
                }
            });
        }

        //滾動加載
        $(window).on('scroll',function(){
            //文檔距離底部的距離小于200px 去加載
            //并且加載完成才能繼續加載

            //items的高度
            var itemsHeight = $items.height();
            //items距離頂部的偏移量
            var itemsTop = $items.offset().top;
            //整個頁面距離頂部的卷曲出去的距離
            var scrollTop = $(document).scrollTop();
            // 瀏覽器的高度
            var winHeight = $(window).height();
            //  瀏覽器底部距離items底部的距離
            var bottom = itemsHeight + itemsTop - scrollTop -winHeight;
            //  判斷按鈕是不是loading狀態
            var loading = $btn.hasClass("loading");
            
            //如果按鈕小于200 且 不是loading狀態就開始加載
            if(bottom < 200 && !loading){
                render();
            }

        })

        render();
    });

需要特別注意的問題

之前我們在靜態加載頁面的時候使用的是window.onload,是為了讓頁面上的資源全部加載完成之后再進行頁面的渲染。否則就會產生頁面重疊的現象。
在動態加載頁面的時候,我們先拿到后臺的數據,然后轉化成html追加到頁面上之后,才開始加載img圖片。這里也遇到了之前的問題。

之所以后面沒有用window.onload那樣的做,是因為原本設置的圖片已經設定可寬和高。img有的設定了250px,有的設定了450px。
但是這樣做不合理,因為有的圖片會變形。

下面提供解決問題的方法:

  1. 等所有的圖片加載完成進行頁面渲染,但是這樣時間會比較長,不合理。
  2. 參考花瓣
    花瓣在加載圖片的時候也進行了寬高的設定,但是這個大小要根據原圖片的尺寸進行大小的縮放。
huaban.png

原來的尺寸是608,現在的寬度是200,那么現在的高度就進行一個換算
現在的高度 = 200 / 806 * 782
width 是現在的寬度

//模板引擎中寫
<img height = "<%=items[i].width * items[i].height / width%>" src = "<%=items[i].path%>" />
//同樣在ajax的success中
 $items.append(template('template',{data:data,width:width}));
這樣width變量就可以使用了。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標簽默認的外補...
    _Yfling閱讀 13,805評論 1 92
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,245評論 4 61
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,353評論 25 708
  • 平行世界,多元生活 Start:2016.9.17~13:00 Finish:2016.9.20~9:20 這是讀...
    林培智閱讀 774評論 4 4
  • 雜草滿目 芳香暗藏 凌亂的心緒 深一腳、淺一腳 驚擾蜂蝶 多情糾纏的紫藤之下 可有萱草黃花 性涼,清熱去驚 母親說...
    馬流云閱讀 327評論 0 1