As an example of how this works at a high level, let’s assume that your application displays a huge list of elements. If that list has a maximum height of 200px and each element row is 20px tall, it can only display a maximum) 10 elements at any time. If we have some pagination techniques that retrieve 50 elements from the backend each time (a small amount of elements compared to the total), we can still visualize a slice size of 10. This enables users to scroll through as they please but saves us from creating and rendering the majority of the elements in the dataset.
There are few concepts to consider when we talk about infinite scroll: slice size, range, pagination, and page size.
The slice size represents the elements that the user can see in the application at any given time. A few more elements must be preloaded in the list besides the elements contained in the current slice in order to produce the smooth effect of the scroll. Slice size won’t change (as long as the list area doesn’t get resized). For instance, if we preload the first 20 elements in a dataset, that range will move a bit as the user scrolls, rendering elements from 4 to 24, 8 to 28, and so on, until the end of the page is reached.
This scrolling technique only renders a subset of the current elements but gives the user the sensation of continuous scrolling. The user won’t notice what is happening in the background.
What about pagination?
One approach is to perform an AJAX request to the server when a specific row is inside the viewport. For example, if we have a page size consisting of 50 elements, we do the request when the 25th row is rendered, and then when the 75th row is rendered, and so on. By the time the user reaches the bottom of the list, the AJAX request will likely finish on time. The exact moment to perform the request might vary. Instead of a specific row, we can measure how close we are to the bottom in pixels. This approach assumes the server maintains a good response time for each async request.
If the AJAX request is not complete when the bottom of the container is reached, the list can render a loading spinner. This enables it to work as an infinite loading list. The user can keep scrolling down when the new data is available since it will be added to the bottom of the list (according to the current visible range). A new AJAX request will be performed when the conditions are met, and the user doesn’t have to manually ask the server for new information. The list could also have a “load more” functionality, so the next elements are retrieved on demand (or a combination of both). The scrolling technique still applies in both scenarios.
Implementation might vary depending on the frameworks that are being used in the UI. For example, if the application uses React, only a maximum of slice_rows elements in the list are created and their keys don’t change over time. When new elements have to render, the rows stay the same—only the inner content is updated. It is not necessary to remove the DOM elements and create new ones; we can reuse the ones we created at the beginning in the first render. This technique has a positive impact on browser performance.
Also, there is no “best” way to determine page or slice size. You should conduct performance analysis to help define better elements restrictions for your application’s specific needs. Moreover, listening to the traditional onscroll event might not offer uniform results or quality performance across different platforms. It would be problematic if the attached event handler executed every time a user scrolled one pixel. Debouncing that handler will improve performance.
To summarize, if you are implementing an infinite scroll solution, make sure you keep only the visible elements in the DOM and pick the right combination of page size, slice size, and range that makes most sense given the nature of your product.