Rendering large data sets in the browser while optimizing for performance and accessibility is a complex problem. The current approach to handling long lists of data is using an infinite scroll pattern to incrementally load and render data just before the data enters the view. This approach comes with trade-offs that we will look at in detail as well as new features and standards that are coming down the pipe that will improve virtual scrolling in the future.
Virtual scrolling has become commonplace across the web. Social media, news aggregators, and photo sites use virtual scrolling to present a list of posts. Map providers render tiles that scroll endlessly in two dimensions. And large data sets with tens of thousands of rows are displayed in data grids or spreadsheets.
Challenges of virtual scrolling
Creating a good virtual scrolling experience is about balancing trade-offs. Rendering too much up-front impacts performance due to the time it takes to render many DOM nodes; the time and bandwidth it takes to load data, images, and other resources; and the memory allocation for content (which may never be viewed by the user). In an ideal scenario, performance is optimized without impacting end user experience.
Early approaches for solving this challenge followed a more traditional pagination model, where the user was presented with an arbitrary number of items such as 10 or 25, and the next block of data would only get rendered when the user explicitly requested the next set of data, usually by interacting with the user interface, e.g. clicking the next page button. Ideally the performance of rendering large or never-ending data sets gets optimized without impacting end user experience.
The pagination approach is simple to implement, but is suboptimal for most use cases as it requires extra user interaction and does not immediately display the next set data nor does it proactively fetch this data to anticipate the end user’s needs.
More modern implementations use an endless scroll approach. The component is responsible for pre-fetching models and getting additional data and resources when necessary. Optimizations were made to determine how many rows of data to render offscreen and when to fetch data to provide a solid user experience. The component was constantly adding, removing, reusing, and rendering rows to prevent the application’s memory from increasing in an unbounded manner.
Clever endless scrolling implementations would add some sort of debouncing mechanism to prevent loading too much data when users would rapidly scroll up and down, with a common pattern being to wait for 15ms of inaction before fetching and rendering more data. All of this necessitated a lot of complexity and coordination. It worked well on even mobile devices with the caveat that only a small portion of the data actually existed on the page and therefore features like “find in page” do not work as expected.
A few years ago efforts began to start providing standards primitives to address these patterns.
The WICG recently introduced the Intersection Observer API which determines whether an element is inside another element or inside the viewport. The Intersection Observer primitive greatly reduces the calculations necessary to determine if an element should get displayed, and it also provides native notifications for when the intersection status changes.
Building on the Intersection Observer foundation, recently a built-in <virtual-scroller> custom element was proposed. As explained in the motivation for the virtual-scroller custom element:
Virtual scrollers are complex and hard to get right. In fact, having a first-class experience with virtualized content is currently impossible, because browsers don’t expose the right hooks: things like accessible landmark navigation, find in page, or intra-page anchor navigation are based solely on DOM structure, and virtualized content is by definition not in the DOM. Additionally, today’s virtualized content does not work with search engine crawlers, which means that sites that care about search engine ranking are unable to apply this important performance technique. This is bad for the web.
The <virtual-scroller> should make the virtual scrolling pattern more robust and simpler to implement. The <virtual-scroller> proposal also provides recommendations for how to best work with varying size data sets and for larger data sets. With these recommendations, developers are still encouraged to implement a virtualization mechanism for loading data. Additionally, the virtual-scroller proposal is not intended to solve the use cases of data grids.
Intersection Observer v2
Work has also begun on version 2 of the Intersection Observer API, intending to solve some of the edge cases with Intersection Observers, including whether an element is hidden or covered by other content or whether it’s not visible due to stylistic modifications such as transform, opacity and filter, etc.
The additions in the Intersection Observer version 2 API are considered more expensive from a performance impact, and the current proposal suggests that developers only opt-in as needed for version 2 capabilities.
The loading attribute
While the Intersection Observer API is powerful, it does not handle the use case of loading an image just before the user scrolls to that portion of the UI. Currently many implementations based on the Intersection Observer API check to see if an element intersects inside a box somewhat larger than the viewport to support this scenario.
For this use case a new API strives to solve this challenge for images and iframe content: the loading attribute. The new loading attribute supports three values:
- lazy (for lazy loading as needed)
- eager (load immediately)
- auto (the browser decides for you)
The lazy attribute should load images a bit before the user scrolls to them so that the end user experience appears to seamlessly load images.
While not every challenge with virtual scrolling has yet been solved through standardization, the foundation on which we build user experiences with large data sets is improving significantly.
If you need help creating and optimizing user experiences with large data sets, please contact us to discuss how we can help!