D3.js version 5 Lazily loading DOM elements
In this example I demonstrate how you can lazily load DOM elements as you scroll through a page using D3.js. The full code for this is available on my website.
Why you might want to lazily load DOM elements
As page size increases the browser will slow down rendering it. This can have detrimental effects when someone is using the page.
One way to reduce the amount of DOM elements on the page is to only draw those that a viewer can see. By doing this once the person has scrolled past elements they can be cleaned up and removed.
This should improve the redraw speed and memory usage of the page, as it needs to store less elements in the DOM.
This will however increase the complexity of the page, and may overall cause it to run slower cumulatively as it will spend more time performing calculations.
Working out where each element will be placed
The big disadvantage of this method is that you need to keep track of where all the elements are on the page. Once you know this you can work out which ones you conditionally want to not display.
In my example I am going focus on y axis scrolling (standard scrolling). As elements get scrolled offscreen they will be removed from the page.
For my example I am creating rectangles and when created, storing the maxium and minimum y positions. Here this is done in a loop that creates my data.
for(var i = 0; i < ROWS; i++) {
rawData.push({"index": i, "color": colorScale(i), "startY": i * ROW_HEIGHT, "endY": (i+1) * ROW_HEIGHT});
}
Calculating these values is trivial in this case as my SVG is placed at (0,0)
on the page. However if your SVG is not placed here you will also need to calculate the position of the SVG and add this to your Y values.
These values are stored as the position of these elements never change. This is then later used to decide whether it should be rendered or not.
Only rendering those elements currently shown
Once I have calculated the Y positions of the elements I can create my filter function to decide what is within the bounds of my user scroll.
Here before I pass my data array to D3 I call filter()
on it to remove the elements I dont want to render.
//This will be called for each element, if this returns true it will keep the element in the array
//Otherwise it will remove it from the newly returned array
function filterVisibleElements(d) {
return (d.startY >= minYWithPreloading && d.endY <= maxYWithPreloading)
|| (d.startY <= minYWithPreloading && d.endY >= minYWithPreloading)
|| (d.startY <= maxYWithPreloading && d.endY >= maxYWithPreloading)
;
}
Each element will be tested with the above function to decide whether it should be drawn or not.
For this I have defined two variables, minYWithPreloading
and maxYWithPreloading
. These define the minimum and maximum Y that I want all elements to sit in.
These have been created by taking the current Y positions of the screen and padding it by 1.5 times the viewpoint. This renders the elements before the user scrolls to it, meaning that if they scroll fast the elements should always be rendered.
The three tests used to decide whether an element should be rendered are:
- Does the element sit entirely between the two bounds
- Does the element sit at the top of one of the bounds, with part of it inside the bound
- Does the element sit at the bottom of one of the bounds, with part of it inside the bound
If the elements match these criteria, they are returned into the data array that is passed to D3.
var filteredData = rawData.filter(filterVisibleElements);
var rowRectSelection = dynamicSvg.selectAll(".rowsMain").data(filteredData);
var rowRectScrollSelection = scrollSvg.selectAll(".rowsScroll").data(filteredData);
Every time the user scrolls the update function is called and the elements visible are updated.
Summary of lazily loading DOM elements
First when the elements are created their Y positions are calculated. These are stored as they will be constant throughout the lifetime of the page.
Once this is calculated the update function is called for the first time. This draws the first set of elements. Once this has been done any further scroll events will trigger an update, refreshing what elements are displayed.
The right SVG is used to demonstrate what elements have been loaded on the page. Here the red box is used to show the range of elements that are loaded for the page. The blue box is demonstrating the current view of the page.
This relatively simple example allows expansion to more complex methods of drawing a page. It is important to note that if the update method is quite expensive you may need to buffer DOM updates. This may require writing a small buffer function to delay updates if multiple scroll events are received at the same time.
The full code is available on my website and if you have any questions ask below.