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.

Leave a Reply