Performance & Reliability
DataGridXL takes great pride in its performance. We really wanted to achieve smooth-as-butter scroll (and edit) performance for data sets with thousands of rows (records) and 60+ columns (fields).
Besides performance, we had a number of other requirements:
- The component should "blend into" any web application, which means the component should be as "accessible and zoomable" as possible (meaning CTRL+F would work to highlight cell values, and zooming the browser would not result in a blurry or pixelated component).
- We wanted the grid to be completely reliable. For great scroll performance, we knew we had to adopt a "virtual rendering" model. We didn't want to mess with data-attributes on DOM nodes. Instead, we wanted to implement a store & state model from the start, inspired by React at the time.
- The component had to be lightweight, without any dependencies. We're not the biggest fans of 1MB Javascript libraries. Who accepts a 1MB dependency when the visual output of the component resembles a simple HTML <table> tag?
Those were the four major features we were looking for: performance (both scroll and edit), developer friendliness, reliability and being lightweight & dependency-free.
Study how others do it
First thing; we looked at other components on the market to study how they do their rendering. We have looked at similar components, including well respected Ag-Grid and Handsontable, but found that they did not provide the render-speeds we were looking for.
Good for us, because if only one of these products had superb scroll and edit performance, there would have been no reason to continue our efforts in creating our data grid in the first place.
We looked at Google Spreadsheets and Excel Online as well. These apps have decent scroll performance (Google Spreadsheet edit performance can be really slow though!), but as they're largely made in Canvas, it was a little hard to find out how these apps were made.
Diving deeper
After our initial study, we experimented with all kinds of approaches and renderers. The four approaches we looked into were:
- Numerous "plain DOM" approaches (including <table> and <div> elements).
- 2D Canvas, as this is used by Google Spreadsheets and Excel Online for at least parts of their spreadsheet application interfaces.
- SVG. As SVG uses absolute positioning by default, we thought reflows & repaints might be more efficient compared to regular HTML DOM.
- We even looked at 3D WebGL for a brief amount of time. (That's how desperate we were to get good performance).
Approach 1: plain DOM
The plain DOM approach is the most common, for good reason. The benefits are that HTML DOM is the main technology in most web applications (let alone web pages). Also, DOM text is zoomable and searchable. It also makes it easy for any beginner web developer to include and modify the data grid component.
We naively started out displaying cell values in a <table> tag. The biggest plus of using a table tag is that it best describes our component for any SEO-purposes. However, DataGridXL is meant for input first-of-all (mostly targeted towards admin areas and SaaS applications). This means that SEO is not at all important in our case.
We created a table with 30 rows and 10 columns. That is already 330+ different nodes inside the parent table node (10*30 <td> cell nodes inside 30 <tr> row nodes). That is fine. Modern-day browsers can display that many nodes without much effort.
However, we're looking for scroll performance, not just load-time performance. We really wanted to create a grid that could support thousands of rows without a hiccup.
Virtual Scrolling
We implemented a simple virtual scrolling system to test our basic DOM approach. We created a viewport node that displayed 30 rows of equal height. All rows contained 10 cells. Scrolling the table would not add or remove any cells, but would replace all <td> inner values using innerHTML or textContent calls.
It wasn't bad, but not nearly snappy enough. Also, for this basic test we were only replacing text contents. What about cell background colors, different column widths, alignments, et cetera? It was already a bit slow just replacing text values. And this was just in a 30x10 viewport. Of course it was twice as slow with 60 rows in the viewport.
We took another look at Ag-grid among other virtual scrolling grids and demos. They all implemented the same technique: instead of replacing the values of each <td>, the approach consisted of visually moving row nodes (mostly using CSS transforms), and append or recycle any incoming rows that were previously outside the viewport.
Flash of no-content
It worked great when slowly scrolling the table. However, when scrolling faster, rows would come in only after a certain amount of time. Javascript and the DOM could not keep up with our scrolling speeds. We didn't like it at all. It's the 21st century and we can't make a scrolling table?
All the virtual scrolling components and demos had the same side-effect. Somehow, there was just no way to avoid this annoying "spawning rows" artifact. We were ready to accept this side-effect, but we found out that it gets much worse when horizontal (column) scrolling is added in the mix.
Imagine our viewport of 30 rows × 10 columns. Imagine we're scrolling fast, but not super-fast. In one frame (~16ms) we scroll from rows 1-30 to rows 21-50. This means that the top 20 rows of our previous viewport are discarded, the last 10 (rows 21-30) are moved up, while 20 new row nodes have to be appended (rows 31-50). These new row nodes might be cached in memory to speed up the rendering.
Here we'll already see the little "flash of no-content" before the 20 new row nodes are rendered and appended. Annoying. Imagine now that we're not only scrolling from rows 1-30 to rows 21-50 in a single frame (only ~16ms!), but at the same time we're scrolling columns 1-10 to columns 6-15. This means that all row nodes will also need to append/reposition/recycle 5 new child nodes (representing the cells/columns). So we're not just adding/recycling 20 (row) nodes in a single scroll frame, but another 150 (5*30) cell nodes as well.
Rendering 20 nodes in a single paint frame might be OK (but only on the latest Macbook). But rendering 100+ nodes in a single paint frame is just impossible.
Believe us. We've tried everything. We've tried to replace <table> nodes with divs, we tried documentFragments, CSS 3D translations and absolute positioning, cached DOM nodes, cloned DOM nodes, recycling DOM nodes, textContent vs innerHTML vs appendChild.
Nothing we tried made us come closer to the results that we were after. Scrolling just looked and felt clumsy. We wanted our scroll performance to seem native, like a spreadsheet application on desktop or smartphone.
Disappointed in the DOM, we looked at Canvas next...