As a front end, we usually encounter a large category of work is to present various data, and list is one of the basic and universal form, whether it is search engines like Google, Baidu, to user communities like Facebook, Twitter, Nuggets, or local services like Meituan, Ele. me. Lists are one of the most important forms of presentation.

The bottom of the load

The interactive forms of list loading data mainly include paging and bottom loading. Since bottom-loading is more suitable for mobile devices and is more suitable for situations where the number of items in a list such as recommendations is uncertain, bottom-loading has become a relatively mainstream choice at present.

The core of bottoming loading is to determine when the conditions of bottoming are met. The most intuitive method is to judge the scrolling elements in the process of scrollingscrollTop+clientHeightWhether it is approaching or exceedingscrollHeight. To review the definitions of these attributes:

Since the Scroll event fires very frequently, we need to throttle the callback function to reduce the frequency of execution in order to improve performance. At the same time, in the process of calling the back-end interface to wait for the result, even if the scrolling position meets the condition again, there is no need to initiate the interface request again.

We submitted the code and were pleased to see it loading silky and smooth. Until the guy at the back of the next seat ran up and said, “I maximized the window, why there is no content at the bottom?” I saw that the interface did not report an error.

Well, it turns out that not only scrolling, but also element size changes need to be monitored! So what to do? The browser provides the ResizeObserver interface, and we can create a ResizeObserver and use it to monitor our scroll element (or, in most cases, directly monitor the body element) as the element size changes, still based on the scrollTop, The relationship between clientHeight and scrollHeight determines whether loading is required.

const resizeObserver = new ResizeObserver(entries= > {
  if(entries[0].scrollTop + entries[0].clientHeight >= entries[0].scrollHeight) {
    // Load morethrottledLoadMore(); }}); resizeObserver.observe(document.querySelector('.scroll'));Copy the code

There’s another way to tell where you’re going to roll. We can place a transparent footer at the bottom of the list and use IntersectionObserver to determine whether the footer appears in the visible area. This way you don’t have to deal with element size changes.

const intersectionObserver = new IntersectionObserver(entries= > {
  // If footer is not visible, return
  if (entries[0].intersectionRatio <= 0) {
    return;
  }
  // Load more
  throttledLoadMore();
});
intersectionObserver.observe(
  document.querySelector('.scrollerFooter'));Copy the code

Compared to the page, in the form of the bottom of the loading of data consistency more demanding, because different pages of data will be shown in the same list, if in the interval of different page request, data has changed, so could lead to a different page data consistency is damaged, there is data between often leads to a request. In this case, the front end needs to redo the data based on the ID to avoid showing duplicate elements in the list.

Virtual rolling

The bottom loading has been achieved, and it’s nice to pull down, pull down, pull down, and why, it feels more and more like Chuck?

Open up the console and, boy, the page is full of DOM nodes

Each of our items basically looks like ⬇️

Each item has an avatar, a user name, a brief introduction, and a variety of information. Before each information, there is an icon. A simple calculation shows that an item has 20 DOM nodes

Once you get to 100 or so items, the light list of the page already has 2000 nodes, and if there’s some animation or something… Performance is going to be a big problem!

The fact that our screen is so big, should we just render the nodes we can see and slide the buffer up and down? Each roll, we only need to recalculate the current location (still is calculated via the scrollTop) and sliding buffer should display elements, while the rest of the up and down for filling out directly with a long blank element respectively, in this way, even if we have to decline, rendering the number of elements have been won’t change. The overall DOM structure should look like this:

<div style="height: 2000px;"></div>
<div style="height: 50px;"><! -- content item 41 --></div>
<div style="height: 50px;"><! -- content item 42 --></div>
<div style="height: 50px;"><! -- content item 43 --></div>
<div style="height: 50px;"><! -- content item 44 --></div>
<div style="height: 50px;"><! -- content item 45 --></div>
<div style="height: 50px;"><! -- content item 46 --></div>
<div style="height: 50px;"><! -- content item 47 --></div>
<div style="height: 50px;"><! -- content item 48 --></div>
<div style="height: 50px;"><! -- content item 49 --></div>
<div style="height: 50px;"><! -- content item 50 --></div>
<div style="height: 2000px;"></div>
Copy the code

Alternatively, we can achieve virtual scrolling by rendering the list in an absolutely positioned manner. All we need to do is set the outer scroll container to a relatively positioned element, give the height of the container based on the total number of elements, and then position them by TOP based on the current location and the elements that should be displayed in the slide buffer. React-window uses this implementation. The same DOM structure as the long blank element scenario above would look like this:

<div style="height: 4500px; position: relative; overflow-y: auto;">
    <div style="height: 50px; top: 2000px;"><! -- content item 41 --></div>
    <div style="height: 50px; top: 2050px;"><! -- content item 42 --></div>
    <div style="height: 50px; top: 2100px;"><! -- content item 43 --></div>
    <div style="height: 50px; top: 2150px;"><! -- content item 44 --></div>
    <div style="height: 50px; top: 2200px;"><! -- content item 45 --></div>
    <div style="height: 50px; top: 2250px;"><! -- content item 46 --></div>
    <div style="height: 50px; top: 2300px;"><! -- content item 47 --></div>
    <div style="height: 50px; top: 2350px;"><! -- content item 48 --></div>
    <div style="height: 50px; top: 2400px;"><! -- content item 49 --></div>
    <div style="height: 50px; top: 2450px;"><! -- content item 50 --></div>
</div>
Copy the code

Present value

Our implementation of the list has been praised by everyone, in the project review, the product sister excitedly said, since your list can be calculated according to the location of the rendering of which elements, so can you calculate the page on the display of which elements?

Of course it can! The overall logic is very similar to virtual scrolling, which is the element that the computation should present in the interface. Note, however, that the same element in the same list should only be evaluated once. Therefore, for all the elements that we have counted and displayed, we need to cache their ids to avoid double calculation. When the list is refreshed, the ID cache needs to be cleared to count it again. Finally, every time a new element is presented, we invoke the back-end commit (and throttle), and let the back-end guy do the calculation.

In the next article, we’ll delve into throttle and Debounce functions that are commonly used by the front-end to aid in performance tuning, from concept to usage to implementation. Welcome to continue to pay attention to us!