JS+HTML to realize dynamic infinite scrolling of lists
problem
In HTML page development projects, scrolling lists are often encountered - CSS is set when the actual width or height of the content to be displayed exceeds the width or height of the container.
overflow-x:auto; overfow-y:auto;
When there is less content in the scrolling list, we can load all the content in the list container at once.
When there are more contents in the scrolling list, the data is loaded step by step by paging. There are two ways to load the data.
1. By adding a label element indicator at the end of the list and scroll events to the list, we can monitor whether the indicator element is visible. If visible, submit the request to load the next page of data, append to the end of the list content. In this way, the data can be loaded indefinitely until the data is fetched.
2. Add a paging toolbar at the bottom of the list. The user requests the data of the specified page and replaces it with the content of the current list.
For mode 1, users want to be friendly to each other. The loaded data will not be loaded repeatedly and the requested resources are small. However, when the amount of equivalent is large, the DOM elements on the page will increase continuously, and the memory consumption will also increase.
Mode 2 Users have to re-request data for every page they turn, even if they want to re-view the data they have seen. There are more requests, the advantage is that the number of DOM elements on the page is fixed, and less memory is occupied.
So is there a way to combine the advantages of mode 1 and mode 2, discard the disadvantages, and integrate the optimization?
~ ~ Yes! There are ways to ~~
Solution:
Fixed the width and height of the current list, fixed the content of the list, deleted or added elements dynamically when the list scrolls to meet the conditions, and kept the fixed number of elements in the correct display of the visual area of the list.
Lists can scroll indefinitely, data can be loaded indefinitely, and DOM elements can be kept in a certain number.
Let's release the implementation code first.
<!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]); } //Update this field as a token of whether you need to update the height of main-content //lastMaxItemIndex=Math.max(lastMaxItemIndex,lastIndex); //At the same time, the 10 elements behind removefirst-item $(".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(){ //Delete the bottom 10 elements 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]); } //Delete the 10 elements at the end $(".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; //Add 10 new elements to the header }); } 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>
Part HTML
infinity-scroll specifies list containers, CSS sets them to scroll horizontally and vertically automatically
main-content is a rolling content component whose height increases with the increase of actual content.
. item is a single item element for scrolling content
The. first-item is the first element of the content currently placed in the list container and the identification element currently scrolling to the top.
last-item is the last element of the content currently placed in the list container and the identification element that is currently scrolling to the bottom.
Place three groups of elements as the initial list content. Why are three groups? To ensure smooth scrolling, the current list window displays a group, rolls up a group, and hides a group at the bottom.
Part JS
$('.infinity-scroll').on("scroll",infinity_scrollFun);
Adding scroll event listening infinity_scrollFun to the list
infinity_scrollFun function
Calculate the height of the list container and the distance from the top of the viewpoint
if(!infinity_scroll_height){ infinity_scroll_height=$(".infinity-scroll")[0].getBoundingClientRect().height; infinity_scroll_top=$(".infinity-scroll")[0].getBoundingClientRect().top; }
Calculate the location of. last-item, and do nothing if the data is being loaded
if(isLoading){ return; }else{ last_item_top=$(".item.last-item")[0].getBoundingClientRect().top; last_item_top=last_item_top-infinity_scroll_top; }
If. last-item appears in the list window, load the new data
if(last_item_top<=infinity_scroll_height){ isLoading=true; appendNewItems($(".main-content")[0].getBoundingClientRect().height,$(this).scrollTop()); return; }
If. first-item appears in the list window, then restore has loaded the original data.
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 function
Keep the total number of elements, delete the 10 elements after. first-item, add new content to the bottom, and keep the actual height of the content and the location of the content displayed in the list window.
$(".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 }); } //Place the page scroll when setting scrollTop, remove scroll event listeners first, and then add listeners $('.infinity-scroll').off("scroll",infinity_scrollFun); $('.infinity-scroll').scrollTop(scroll_top); $('.infinity-scroll').on("scroll",infinity_scrollFun);
restorePreItems function
Similar to the appendNewItems function, it just deletes 10 elements at the bottom, adds 10 elements to the head, and keeps the actual height of the content and the location of the content displayed in the list window.
//Delete the 10 elements at the end $(".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);
Areas to be improved
The above assumption is that the height of the item in the content area is the same, and the number of items added each time is the same. If the height of content item changes dynamically;
At the same time, the reuse of DOM elements in pages is not achieved. In fact, deleted elements can be reused as elements to be added, only the data display content can be changed.
The code needs to be modified accordingly. Let's leave it to the small partners for improvement.