This paper purpose

Long list rendering and infinite dropdowns are one of the most common problems in front end development, and this article will introduce a simple, smart, and efficient way to do it. Without further ado, look at the picture below, maybe you can find something?

Do you notice anything from the above diagram, such as only the visible part of the DOM is rendered, and only the padding of the outer container changes as you scroll?

The previous point makes sense. For performance reasons, it is impossible to render all the list elements in a long list (or even an infinite drop-down list); The latter point is one of the core solutions presented in this article!

Without keeping you in suspense, there are two elements of the plan:

  • Intersection Observer
  • padding

With the elements stated, perhaps you can try to start thinking and see if you can guess the implementation.

Plan to introduce

Intersection Observer

The basic concept

It has never been easy to detect the visible state of an element or the relative visible state of two elements. Traditional solutions are complex and performance expensive, such as listening for scroll events, then querying the DOM to get the height of the element, the position, the height from the window, and so on.

This is the problem that the Intersection Observer solves. It provides developers with a convenient new way to asynchronously query the location of elements relative to other elements or Windows, eliminating costly DOM queries and style reads.

compatibility

It’s mostly not compatible with Safari, which requires 12.2 and above to be compatible, but thankfully, polyfill is available.

Some application Scenarios

  • Lazy loading implementation while the page scrolls.
  • Infinite dropdown (the implementation of this article).
  • Monitor the exposure of certain advertising elements to make relevant statistics.
  • Some interactive logic is implemented by monitoring whether the user’s scrolling behavior reaches the target location (such as pausing when a video element is rolled to a hidden location).

Implementation of padding scheme

With the Intersection Observer in mind, take a look at how to use the Intersection Observer + padding to achieve an infinite drop down.

First overview of the general idea:

  • Listen for the first and last elements of a fixed length list to enter the window;
  • Update the sequence number corresponding to the first element rendered in the current page;
  • According to the above serial number, the target data element is obtained, and the list content is re-rendered into the corresponding content;
  • Container padding adjustment to simulate scrolling implementation.

Core: Use the padding of the parent element to fill in more and more DOM elements as the infinite drop down, leaving only a certain number of DOM elements above and below the window area for data rendering.

1. Listen for the first and last elements of a fixed length list to enter the window

// Create an observer
this.observer = new IntersectionObserver(callback, options);

// Watch the first and last element of the list
this.observer.observe(this.firstItem);
this.observer.observe(this.lastItem);
Copy the code

Take the example of rendering a fixed list of 20 elements in a page. The first and last elements are observed using the Intersection Observer. When one of them re-enters the window, the callback function fires:

const callback = (entries) = > {
    entries.forEach((entry) = > {
        if (entry.target.id === firstItemId) {
            // When the first element enters the window
        } else if (entry.target.id === lastItemId) {
            // When the last element enters the window}}); };Copy the code

Update firstIndex of the first element of the current page rendering.

For example, we use an array to maintain the data that needs to be rendered to the page. The length of the array grows as new data is requested, and a certain number of elements, say 20, are always rendered. So:

  • 1. First render the array from 0 to 19, and the corresponding firstIndex is 0;

  • When the element 19 (lastItem from the previous step) enters the window, we render the next 10 elements. When we render the element 10-29, our firstIndex is 10.

  • 3. Next time, when element 29 enters the window, render 10 more elements, that is, render elements 20-39, then the firstIndex will be 20, and so on.

// We cache the original firstIndex
const { currentIndex } = this.domDataCache;

// Take half of all elements in the entire container as each render increment
const increment = Math.floor(this.listSize / 2);

let firstIndex;

if (isScrollDown) {
    // The sequence number increases when scrolling down
    firstIndex = currentIndex + increment;
} else {
    // The sequence number decreases when scrolling up
    firstIndex = currentIndex - increment;
}
Copy the code

In general, you update the firstIndex to know what data should be fetched and rendered next, based on the scrolling of the page.

3. According to the above serial number, obtain the corresponding data elements, and re-render the list into new content

const renderFunction = (firstIndex) = > {
    // offset = firstIndex, limit = 10 => getData
    // getData Done => new dataItems => render DOM
 };
Copy the code

This part is to query the data based on firstIndex and then render the target data to the page.

4, padding adjustment, simulation rolling implementation

Now that we have the update of the data and the update of the DOM elements, how do we achieve the infinite drop down effect and the scrolling experience?

Imagine that the most primitive, direct and brutal way to do this is to take 10 new data elements and then shove 10 new DOM elements into the page to render the data.

But now, compared with the rough scheme above, our scheme is as follows: for the 10 new data elements, we use the original DOM elements to render, replacing the invisible data elements that have left the window; Where more DOM elements should have further stretched the height of the container, we use padding to simulate this.

  • Scroll down
// Padding increment = height of each item x number of new data items
const remPaddingsVal = itemHeight * (Math.floor(this.listSize / 2));

if (isScrollDown) {
    // paddingTop Add, fill the top position
    newCurrentPaddingTop = currentPaddingTop + remPaddingsVal;

    if (currentPaddingBottom === 0) {
        newCurrentPaddingBottom = 0;
    } else {
        // If the original paddingBottom is subtracted, there will be a scroll to the bottom element to replacenewCurrentPaddingBottom = currentPaddingBottom - remPaddingsVal; }}Copy the code

  • Scroll up
// Padding increment = height of each item x number of new data items
const remPaddingsVal = itemHeight * (Math.floor(this.listSize / 2));

if(! isScrollDown) {// paddingBottom is added to fill the bottom position
    newCurrentPaddingBottom = currentPaddingBottom + remPaddingsVal;

    if (currentPaddingTop === 0) {
        newCurrentPaddingTop = 0;
    } else {
        // If the original paddingTop is subtracted, there will be a scroll to the top element to replacenewCurrentPaddingTop = currentPaddingTop - remPaddingsVal; }}Copy the code

  • Finally, the padding-setting update and related cache data update
// Container padding reset
this.updateContainerPadding({
    newCurrentPaddingBottom,
    newCurrentPaddingTop
})

// Update the cache of data related to DOM elements
this.updateDomDataCache({
    currentPaddingTop: newCurrentPaddingTop,
    currentPaddingBottom: newCurrentPaddingBottom
});
Copy the code

Thinking summary

Program summary:

Use the Intersection Observer to monitor the scrolling position of relevant elements, and asynchronously monitor them. Minimize DOM operations as much as possible, trigger callback, and retrieve new data to update page elements. In addition, adjust the container padding to replace more and more DOM elements. The final implementation of the list scrolling, infinite drop down.

A comparison of related schemes

Here is a basic comparison to the better known library – iScroll’s infinite drop down scheme. Before this comparison, let’s give an overview of iScroll infinite’s implementation:

  • IScroll obtains the scroll distance by listening for traditional scroll events, and then:

    1. Set translate to the parent element to move the whole content up (down);
    2. Based on this scroll distance, we calculate that the child element has been rolled out of the window, and determine whether the child element that has left the window should be moved to the end, and translate it to move to the end. This is like a circular queue, with the top element out of the window first, but moving to the end as the scroll progresses, resulting in an infinite drop down.
  • Related comparison:

    • The Intersection Observer listens to the child element to leave the window by setting the parent padding. The other is to listen for traditional scroll events, get the scroll distance, and do a series of calculations to set translate for the parent and child elements. Obviously, the former looks more concise.
    • Performance Comparison: I know that when it comes to comparison, performance is definitely on the forefront of your mind. The key to performance comparison is the Intersection Observer. Because the difference in performance between the padding and translate Settings is small, but I think the padding is cleaner? The Intersection Observer removes all the logic associated with the scroll layer. You no longer need to retrieve DOM properties such as the scroll distance, and you no longer need to perform a series of complex calculations related to the scroll distance. And synchronous scroll events are triggered asynchronously. You don’t need to do extra logic like stabilization, which is a performance improvement.

Existing defects:

  • The padding calculation depends on the fixed height of the list item.
  • This is a synchronous rendering solution, that is, the current adjustment to the container padding does not compute the asynchronously obtained data, only the user’s scrolling behavior. This seems a little out of line with the actual business scenario. Solution:
    • 1. Use Skeleton Screen Loading to render data elements synchronously, which is not affected by asynchronous data acquisition. That is, when the data request is not completed, some pictures are used for placeholder, and then replaced after the content is loaded.
    • 2. Scroll to the destination, block the container padding (i.e. the infinite drop down occurs) until the data request is complete, and use loading GIF to prompt the user to load the state. This solution is relatively complicated, however, you need to set the container padding in full consideration of the unpredictable scrolling behavior of the user.

Extension development

  • I want you to think about it, there’s an infinite pull down, so how do you adjust the implementation of an infinite pull up based on this scenario?
  • How can the existing scheme be optimized if the Intersection Observer is used in iScroll?

Code implementation

  • Complete code implementation reference

Refer to the article

  • Intersection Observer API
  • IntersectionObserver ‘s “Coming into View
  • Infinite Scroll ‘ing the right way

This article is published by netease Cloud Music front end team. Any unauthorized reprint of the article is prohibited. We’re always looking for people, so if you’re ready to make a career change and you love cloud music, join us