Mastering Virtual Scrolling: A Guide to Efficient Data Visualization
Understanding Virtual Scrolling
Imagine having a dataset of 100,000 items that you want to display as a scrollable list without pagination. Rendering all rows at once would overload the DOM, consume excessive memory, and degrade the app’s performance. Virtual scrolling solves this problem by displaying only a small portion of data at a time, while emulating the rest via top and bottom padding elements. Each time the user scrolls, the content is rebuilt: new items are fetched, old ones are destroyed, and padding elements are recalculated.
Building a Virtual Scroller Component
To create a reusable React component, we’ll focus on understanding the core principles and building a small component to satisfy basic requirements. Let’s define the conditions:
- The number of items in the dataset is known and fixed.
- The height of a single row is constant.
- A synchronous data flow from our app to the scroller component is guaranteed.
Infrastructure
To use our VirtualScroller component, we’ll need to pass virtualization settings, provide a data flow mechanism, and define the row template.
Settings: We’ll define a single static object with fields that determine the desired behavior and reflect the initial conditions.
const settings = {
itemsPerPage: 20,
rowHeight: 30,
bufferSize: 40
};
Data Flow: We’ll pass a method that provides a portion of our dataset to VirtualScroller, which will request data via this method.
const getData = (limit, offset) => {
// Return a portion of the dataset
};
Row Template: A simple template that displays the text property, depending on the app’s unique needs.
const rowTemplate = (item) => {
return <div>{item.text}</div>;
};
Building the Virtual Scroll Component
Now that we’ve set up the infrastructure, let’s build the VirtualScroller component.
Render: We’ll need a viewport element with constrained height and overflow-y: auto style, two padding elements with dynamic heights, and a list of buffered data items wrapped with row templates.
<div className="viewport" style={{ height: '300px', overflowY: 'auto' }}>
<div className="padding-top" style={{ height: `${topPaddingHeight}px` }}></div>
<ul>
{bufferedItems.map((item) => (
<li key={item.id}>{rowTemplate(item)}</li>
))}
</ul>
<div className="padding-bottom" style={{ height: `${bottomPaddingHeight}px` }}></div>
</div>
State: We’ll initialize the inner component’s state with four properties: three heights and the current portion of data.
this.state = {
topPaddingHeight: 0,
bottomPaddingHeight: 0,
bufferedItems: [],
startIndex: 0
};
Scroll Event Handling: We’ll implement the runScroller method to fetch data items and adjust padding elements.
runScroller = () => {
const { startIndex, bufferSize } = this.state;
const endIndex = startIndex + bufferSize;
const newItems = getData(bufferSize, startIndex);
this.setState({
bufferedItems: newItems,
topPaddingHeight: startIndex * settings.rowHeight,
bottomPaddingHeight: (settings.totalItems - endIndex) * settings.rowHeight
});
};
Math Behind the Scenes
To calculate the new portion of the dataset, we’ll use the get method with two arguments: limit and offset. The index is calculated considering the top outlet, and the height of the top padding element is taken via a number of rows before the index multiplied by the known height of the row.
const newIndex = Math.floor(scrollTop / settings.rowHeight);
const newStartIndex = newIndex - (newIndex % settings.bufferSize);
Summary and Future Developments
Our VirtualScroller component can virtualize a fixed-size dataset, assuming the row height is constant. It consumes data using a special method and accepts the template and static settings properties that impact the view and behavior. To make our implementation even better, we could propose developments such as:
- Checking all input parameters and throwing meaningful errors
- Default settings and caching
- Allowing infinite datasets and asynchronous data flow
- Dynamic datasource and viewport settings
- Unfixing row height and providing methods to manipulate the scroller runtime
The world of virtual scrolling is vast, and there’s still much to explore. By building a solution from scratch, we’ve increased our abilities in applying the virtual scroll technique.