Implementing Infinite Scrolling
Most of the application has some sort of tables, list or similar components which displays thousands of records with some sort of pagination concept. There are various techniques like showing page number links, load more button or loading more records when user reaches the end of the page. A user friendly list is the one which allows user to jump quickly to any page from the current page.
There are various libraries like slickgrid, uigrid, handsontable which creates the table/list quickly but sometimes they are just not the right tool to use in the application and we need some simple self created component to achieve the task which provides more control over the component.
See angular 2 demo here.
I am going to create a simple list to demonstrate the concept. There are roughly 6 steps to achieve a smooth infinite scrolling experience.
- Determining Total Count Of The List :- We should know how many rows are there in the list. The total count should be there pre hand either in the the server API response or we can use the length of array/collection which stores the list. In my example, the count is 1198. If this number cannot be determined before hand, then you can always re-render the grid with the updated count. Re rendering the component is very fast.
- Calculating Total Scroll Height :- Using above two numbers, we can calculate total height of the component which is 1198 * 50 = 59900. We can set the total height of the list DOM element to be 59900px so that browser will show proper scroll bar. I call this element as parent element. demo here.
- Determining Parent Element With Fixed Height :- Next step is to decide where are we going to render the parent element. Inside fixed height DOM element or in the main body. Set the scroll overflow property to auto. In step 3 demo, you can see the main list is rendered inside element with id “list-component”. this element has 500px height with scroll property set to overflow. I call this element as fixed height element.
- Calculating Max Visible Row Elements Count :- This is the most important part of the implementation. This number will be fixed height element’s height divided by row element’s height. In my example, the height of fixed height element is 500 px and each row’s height is 50 px. That means only 10 rows will be visible at any given point of time. Other rows will be off the screen. A grid will only work if its fast. In order to keep it fast, we need to keep only limited number of rows in the DOM tree. If list contains 1000 records then user won't be looking at 1000 records at the same time. At a time user will only be looking at very few records. The idea is to keep only those few records which user is interested in. In the demo, if you inspect the rows in the developer tool, you will realize that not more than 30 elements are there. As you scroll, more row elements will be added and removed to maintain this number. In my demo, this number is 10 (500/50). As user is scrolling up or down, more rows will be added to the DOM tree and other invisible elements will be removed from it. We will keep some extra buffer elements to avoid frequent DOM updates. Instead of 10 row items, will keep around 30 items in the DOM tree, 10 visible rows plus 10 buffer elements on the upper and lower side each.
- Defining Render Function :- In our render function, we will do two things 1> we will render a given range of elements to the DOM tree, 2> once the items are rendered, we will check how many elements are there in the list. Once rendered elements count exceeds 30 items, extra invisible items will be removed. If user is scrolling down, we will append 10 items to the bottom of the list and remove extra items from the top as top most items will be most invisible items. Similarly when user is scrolling up, we will prepend 10 items to the front of list and will remove excess items from the bottom of list. this function is used in conjunction with scroll handler which is described just below.
- Defining Scroll Handlers :- Final step is to implement scroll handler. Inside scroll handler, we need to first determine the scroll direction. It can be easily identified with last scroll top position. If last scroll top is less than current scroll top, user is scrolling down else user is scrolling up. In scroll down handler, there can be three situations.
Scroll Case1 :- Lets say, initial 30 items are rendered in the screen and user is scrolling down slowly. At some point of time, 30th item will become very close to the bottom of the viewport. Since we have only 10 elements visible in the list and 30th item is about to be shown, that means around 30–10 = 20 items are invisible. So will call our render function to render 30–40 items. And render function will remove the extra 10 items from top. I am using data-indexnumber attribute in each row to find next range.
Scroll Case 2 :- lets say we have initial 30 items in the list and user is scrolling little fast, and as a result 30th row comes to the view and there is some gap in the view as few items are not rendered yet. in this case also, we can ask render function to render another 10 items. Same as case1.
Scroll Case 3 :- Lets say, user scrolls very fast and 30th item goes above the viewport, in this case, we don’t know which range to render as last item went to the top and became invisible. Here the current range will be scroll height divided by row element’s height. Lets say user has scrolled down 4500px and last element was visible till 1500px (30 * 50 px), using the scrolling height, current range will be 4500/50 = 90. so in this case, we can render from 75 to 105. Render function will render this range and will remove the initial 30 items.
You can see the scrolling demo here.
I have created the infinite-scrolling component using the above approach. You can use the grid as is or customize it based on your needs.
Note on Positioning
Positioning all rows as absolute. It’s important to set absolute position to each row and relative to the row parent element because when invisible top elements are removed, below elements should not change its position and should maintain the correct offset top position.