JS+HTML實現列表動態無限滾動

JS+HTML實現列表動態無限滾動

問題

在HTML開發頁面工程中,常常會遇到滾動列表-當實際須要顯示的內容寬度或高度超過容器的寬度或高度時,設置CSScss

overflow-x:auto;
    overfow-y:auto;

當滾動列表中的內容比較少時,咱們能夠一次性加載全部的內容到列表容器中顯示。
當滾動列表中的內容比較多,使用分頁加載的方式逐步加載數據,2種方式html

1.經過在列表的末尾添加一個標別元素indicator,和添加列表的scroll事件來監聽indicator元素是否可見,若是可見那麼提交請求加載下一頁數據,append到列表內容的尾部。經過這個方式能夠實現數據的無限加載,一直到數據取完。
2.在列表下部添加分頁工具條,用戶經過請求獲取指定頁的數據而且替換到當前列表裏的內容jquery

對於方式1對用戶想對友好,加載過的數據不會重複加載,請求的資源少,可是當量多時,頁面上的DOM元素量很不斷增長,消耗的內存也會加大
方式2用戶每翻一頁都要從新請求數據,即便對於翻過看過的數據想要從新看也是如此,請求資源多,好處是頁面DOM元素數量固定,佔用內存資源少app

那麼有沒有一種方式結合方式1和方式2的優勢,摒棄缺點,融合優化下呢?ide

~~有!有方法~~

解決方案:
對當前列表的寬高固定,對列表包含的內容高度固定,列表滾動知足條件時動態刪除或添加元素,保持元素數量的固定已經內容在列表可視區域的正確顯示。函數

列表可以無限滾動,數據可以無限加載,DOM元素保持必定數量工具

先放出實現的代碼吧優化

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="libs/jquery.js"></script>

    <style>
        .infinity-scroll{
            width: 300px;
            height: 500px;
            position: absolute;
            left:10px;
            top:10px;
            background-color: #ffffff;
            box-sizing: border-box;
            border:1px solid green;
            overflow-x: hidden;
            overflow-y: auto;
        }
        .main-content{
            position: absolute;
            left: 0px;
            top: 0px;
            width: 100%;
            box-sizing: border-box;
            background-color: #cccccc;
            border: 1px solid #ffffff;
        }

        .item{
            width: 100%;
            height: 50px;
            background-color: #ffffff;
        }

        .item:nth-child(2n+1){
            background-color: green;
        }

        .scroll-wrapper{
            width: 100%;
            background-color: red;
            height: 2000px;
        }

        .hide{
            display: none;
        }

    </style>
    <script>
        $(function(){
            var infinity_scroll_height;
            var infinity_scroll_top;
            var isLoading=false;

            var lastMaxItemIndex=parseInt($(".item.last-item").last().attr("data-items-index"),10);

            function appendNewItems(prev_main_content_height,scroll_top){
                requestAnimationFrame(function(){
                    var documentFragment=document.createDocumentFragment();
                    var i,item;
                    var lastIndex=parseInt($(".item.last-item").attr("data-items-index"),10);
                    $(".item.last-item").removeClass("last-item");
                    for(i=0;i<10;i++){
                        lastIndex++;
                        item=$('<div class="item" data-items-index="0">2</div>');
                        $(item).attr("data-items-index",(lastIndex));
                        $(item).html(lastIndex+1);
                        if(i===9){
                            $(item).addClass("last-item");
                        }
                        documentFragment.appendChild($(item)[0]);
                    }

                    //更新此字段,做爲是否 須要更新main-content的 height 的標誌
                    //lastMaxItemIndex=Math.max(lastMaxItemIndex,lastIndex);

                    //同時removefirst-item後面的10個元素

                    $(".item").slice(0,10).remove();
                    $(".item").first().addClass("first-item");
                    var mainContentPaddingTop=$(".main-content").css("padding-top");
                    mainContentPaddingTop=parseFloat(mainContentPaddingTop.split("px")[0]||0);

                    $(".main-content").append(documentFragment);

                    if(lastMaxItemIndex<lastIndex){
                        $(".main-content").css({
                            "height":prev_main_content_height+10*50,
                            "padding-top":mainContentPaddingTop+10*50
                        });
                        lastMaxItemIndex=lastIndex;
                    }else{
                        $(".main-content").css({
                            "padding-top":mainContentPaddingTop+10*50
                        });
                    }


                    $('.infinity-scroll').off("scroll",infinity_scrollFun);
                    $('.infinity-scroll').scrollTop(scroll_top);
                    $('.infinity-scroll').on("scroll",infinity_scrollFun);
                    isLoading=false;

                });

            }

            function restorePreItems(prev_main_content_height,scroll_top){
                requestAnimationFrame(function(){
                    //刪除底部的10個元素
                    var documentFragment=document.createDocumentFragment();
                    var i,item,mainContentPaddingTop;
                    var lastIndex=parseInt($(".item.first-item").attr("data-items-index"),10);

                    console.log(".item.first-item lastIndex%s",lastIndex);
                    if(lastIndex<=0){
                        isLoading=false;
                        return ;
                    }
                    $(".item.first-item").removeClass("first-item");
                    for(i=0;i<10;i++){
//                        lastIndex--;
                        item=$('<div class="item" data-items-index="0"></div>');
                        $(item).attr("data-items-index",(lastIndex-10+i));
                        $(item).html(lastIndex-10+i+1);
                        if(i===0){
                            $(item).addClass("first-item");
                        }
                        documentFragment.appendChild($(item)[0]);
                    }
                    //刪除尾部的10個元素
                    $(".item.last-item").prevAll().slice(0,9).remove();
                    $(".item.last-item").remove();

                    $(".item").last().addClass("last-item");
                    mainContentPaddingTop=$(".main-content").css("padding-top");
                    mainContentPaddingTop=parseFloat(mainContentPaddingTop.split("px")[0]||0);

                    $(".main-content").css({
                        "padding-top":mainContentPaddingTop-10*50
                    });
                    $(".main-content").prepend(documentFragment);

                    $('.infinity-scroll').off("scroll",infinity_scrollFun);
                    $('.infinity-scroll').scrollTop(scroll_top);
                    $('.infinity-scroll').on("scroll",infinity_scrollFun);

                    isLoading=false;
                    //頭部添加新的10個元素
                });
            }

            function infinity_scrollFun(){
                var last_item_top,first_item_top;
                if(!infinity_scroll_height){
                    infinity_scroll_height=$(".infinity-scroll")[0].getBoundingClientRect().height;
                    infinity_scroll_top=$(".infinity-scroll")[0].getBoundingClientRect().top;
                }

                if(isLoading){
                    return;
                }else{
                    last_item_top=$(".item.last-item")[0].getBoundingClientRect().top;
                    last_item_top=last_item_top-infinity_scroll_top;
                }

                console.log("~~~last_item_top=%s, infinity_scroll_height%s~~~",last_item_top,infinity_scroll_height);
                if(last_item_top<=infinity_scroll_height){
                    isLoading=true;
                    appendNewItems($(".main-content")[0].getBoundingClientRect().height,$(this).scrollTop());
                    return;
                }

                first_item_top=$(".item.first-item")[0].getBoundingClientRect().top;
                first_item_top=first_item_top-infinity_scroll_top;
                console.log("~~first_item_top~~~%s",first_item_top);
                if(first_item_top>=0){
                    restorePreItems($(".main-content")[0].getBoundingClientRect().height,$(this).scrollTop());
                    return;
                }
                console.log("~~~scrollTop~~~"+$(this).scrollTop());
            }

            $('.infinity-scroll').on("scroll",infinity_scrollFun);
        });
    </script>
</head>
<body>

    <div class="infinity-scroll">
        <div class="main-content">
            <div class="item first-item" data-items-index="0">1</div>
            <div class="item" data-items-index="1">2</div>
            <div class="item" data-items-index="2">3</div>
            <div class="item" data-items-index="3">4</div>
            <div class="item" data-items-index="4">5</div>
            <div class="item" data-items-index="5">6</div>
            <div class="item" data-items-index="6">7</div>
            <div class="item" data-items-index="7">8</div>
            <div class="item" data-items-index="8">9</div>
            <div class="item " data-items-index="9">10</div>

            <div class="item " data-items-index="10">11</div>
            <div class="item" data-items-index="11">12</div>
            <div class="item" data-items-index="12">13</div>
            <div class="item" data-items-index="13">14</div>
            <div class="item" data-items-index="14">15</div>
            <div class="item" data-items-index="15">16</div>
            <div class="item" data-items-index="16">17</div>
            <div class="item" data-items-index="17">18</div>
            <div class="item" data-items-index="18">19</div>
            <div class="item " data-items-index="19">20</div>

            <div class="item " data-items-index="20">21</div>
            <div class="item" data-items-index="21">22</div>
            <div class="item" data-items-index="22">23</div>
            <div class="item" data-items-index="23">24</div>
            <div class="item" data-items-index="24">25</div>
            <div class="item" data-items-index="25">26</div>
            <div class="item" data-items-index="26">27</div>
            <div class="item" data-items-index="27">28</div>
            <div class="item" data-items-index="28">29</div>
            <div class="item last-item" data-items-index="29">30</div>

        </div>
    </div>
</body>
</html>

HTML部分

.infinity-scroll指定列表容器,CSS設置其水平不滾動,垂直方向自動滾動
.main-content爲滾動內容組件,其高度隨着實際內容的增多而加大
.item爲滾動內容的單個項目元素
.first-item爲當前放置在列表容器內的內容的第1個元素,同時爲當前滾動到頂部的標識元素
.last-item爲當前放置在列表容器內的內容的最後一個元素,同時爲當前滾動到底部的標識元素this

放3組元素做爲初始的列表內容,爲何是3組呢?爲了保證滾動的流暢性,當前列表窗口顯示一組,捲起一組,下部隱藏一組rest

JS部分

$('.infinity-scroll').on("scroll",infinity_scrollFun);

爲列表添加scroll事件監聽infinity_scrollFun

infinity_scrollFun函數

計算列表容器的高度和離viewpoint頂部距離

if(!infinity_scroll_height){
    infinity_scroll_height=$(".infinity-scroll")[0].getBoundingClientRect().height;
    infinity_scroll_top=$(".infinity-scroll")[0].getBoundingClientRect().top;
}

計算.last-item的位置,若是正在加載數據不執行任何動做

if(isLoading){
     return;
 }else{
     last_item_top=$(".item.last-item")[0].getBoundingClientRect().top;
     last_item_top=last_item_top-infinity_scroll_top;
 }

若是.last-item出如今列表窗口,那麼加載新的數據

if(last_item_top<=infinity_scroll_height){
    isLoading=true;
    appendNewItems($(".main-content")[0].getBoundingClientRect().height,$(this).scrollTop());
    return;
}

若是.first-item出如今列表窗口,那麼restore原來已經加載過的數據

first_item_top=$(".item.first-item")[0].getBoundingClientRect().top;
first_item_top=first_item_top-infinity_scroll_top;
console.log("~~first_item_top~~~%s",first_item_top);
if(first_item_top>=0){
    restorePreItems($(".main-content")[0].getBoundingClientRect().height,$(this).scrollTop());
    return;
}

appendNewItems函數
保持整體元素的個數,將.first-item後的10個元素刪除,添加新的內容到底部,並要保持內容的實際高度及顯示在列表窗口的內容的位置

$(".item").slice(0,10).remove();
$(".item").first().addClass("first-item");
var mainContentPaddingTop=$(".main-content").css("padding-top");
mainContentPaddingTop=parseFloat(mainContentPaddingTop.split("px")[0]||0);

$(".main-content").append(documentFragment);

if(lastMaxItemIndex<lastIndex){
    $(".main-content").css({
        "height":prev_main_content_height+10*50,
        "padding-top":mainContentPaddingTop+10*50
    });
    lastMaxItemIndex=lastIndex;
}else{
    $(".main-content").css({
        "padding-top":mainContentPaddingTop+10*50
    });
}

//放置在設置scrollTop時頁面滾動,先移除scroll事件監聽,而後再添加監聽
$('.infinity-scroll').off("scroll",infinity_scrollFun);
$('.infinity-scroll').scrollTop(scroll_top);
$('.infinity-scroll').on("scroll",infinity_scrollFun);

restorePreItems函數
功能和appendNewItems函數相似,只是刪除底部的10個元素,添加10個元素到頭部,並要保持內容的實際高度及顯示在列表窗口的內容的位置

//刪除尾部的10個元素
$(".item.last-item").prevAll().slice(0,9).remove();
$(".item.last-item").remove();

$(".item").last().addClass("last-item");
mainContentPaddingTop=$(".main-content").css("padding-top");
mainContentPaddingTop=parseFloat(mainContentPaddingTop.split("px")[0]||0);

$(".main-content").css({
    "padding-top":mainContentPaddingTop-10*50
});
$(".main-content").prepend(documentFragment);

$('.infinity-scroll').off("scroll",infinity_scrollFun);
$('.infinity-scroll').scrollTop(scroll_top);
$('.infinity-scroll').on("scroll",infinity_scrollFun);

待改進的地方

上面的假設內容區的item的高度都是相同,而且每次加在的item數量都是相同的。若是內容item的高度是動態變化的;
同時沒有作到頁面DOM元素的複用,其實徹底能夠複用刪除的元素做爲將要添加的元素,只是變動其中的數據顯示內容;

代碼須要作相應的修改,就留給小夥伴們改進吧:)

相關文章
相關標籤/搜索