Five Fast JavaScript Data Grids — A Performance Review
Which one is your favorite?
Join the DZone community and get the full member experience.
Join For FreeIt is hard to imagine a modern web app that does not involve a data grid component to display and filter the data. In this article, we compare the performance of some of the most popular JavaScript grid components on the market, measuring both the initial render time and the scrolling performance. We also describe the methodology used and there is a GitHub repo where you can see the code used.
The Grid Components
The following popular data grids were used in this comparison:
- Ag-grid (ag-grid.com)
- Bryntum Grid (bryntum.com/products/grid)
- DevExtreme Grid (js.devexpress.com/Overview/DataGrid)
- DHTMLX Grid (dhtmlx.com/docs/products/dhtmlxGrid)
- Ext JS Modern Grid (sencha.com/products/extjs)
Benchmarking Methodology
To make the comparison as fair as possible the following actions were taken:
- Two test cases were created per grid, one with locked/fixed/pinned columns and one without.
- The same dataset was used for all grids, consisting of 10,000 JSON objects with ten fields each.
- Columns were configured as similar as possible for all grids:
- Ten columns, some with custom settings:
- One column with cells that has their background color set from data.
- One column with a custom progress bar.
- One date column.
- One boolean column displaying Yes/No.
- The first three columns locked/fixed/pinned in the relevant test case.
- Ten columns, some with custom settings:
- Some grids ship with features enabled by default, some require you to manually enable them. For fairness, all non-essential features such as sorting, grouping, filtering and so on were turned off
- All grids were rendered into a 1280 x 1024 px sized container.
- All measurements were taken on a 2016 MacBook Pro 13" (2 GHz Intel Core i5, 8GB RAM).
- Scrollbars were turned on.
Measuring
JavaScripts performance.now()
was used to measure time in the benchmarks.
Initial Rendering
The following steps were taken to measure initial rendering:
- Everything was first loaded on the page, such as CSS, JavaScript, and the used dataset.
- A timer was started.
- A grid instance was created and populated with the column set and the already loaded dataset.
- When the grid was fully rendered on screen, the timer was stopped.
A small utility class was used to achieve this, the important part of its source:
export class RenderTimer {
static start({ sync = true, callback }) {
this.start = performance.now();
callback && callback();
if (sync) this.stop();
}
static stop() {
const elapsed = performance.now() - this.start;
console.log(elapsed);
}
}
And example of its usage:
// For a grid that renders sync
RenderTime.start({
callback() {
new Grid({
// config...
});
}
});
// For a grid that renders async
RenderTimer.start({
sync : false,
callback() {
new Grid({
// config...
onRenderDone() {
// async rendering done
RenderTimer.stop();
}
});
}
});
Scrolling
To measure scroll performance, two utility classes are used. One that makes the grid scroll on a timer and one that measures the FPS. Below you can see the source for the scroller:
export class Scroller {
static scroll({ element, distance = 50000, speed = 5, maxSpeed = 1000, acceleration = 1, callback, scrollFn }) {
let scrollTop = 0;
const intervalId = setInterval(() => {
if (scrollFn) {
scrollFn(scrollTop)
} else {
element.scrollTop = scrollTop;
}
scrollTop += speed;
if (speed < maxSpeed) speed += acceleration;
if (scrollTop > distance) {
clearInterval(intervalId);
callback && callback();
}
}, 1);
}
}
It either directly scrolls an element or provides a scrollFn
callback for grids that do not have native scrolling. Scrolling starts at 5px per update, accelerating all the way up to 1000px, this way mimicking flick scrolling on a touchpad or an unlocked mouse wheel. At a predefined distance of 50,000px, the scrolling stops and a callback
is called.
Usage example:
Scroller.scroll({
element : document.querySelector('.scroller-selector'),
callback() {
// scrolling done
}
});
Please note that programmatically triggering the scroll-like this makes it happen on the main thread, in sync with the UI. When an actual user scrolls using the touchpad, it happens async on another thread. The difference might not be obvious, but because it is done programmatically here the UI will always be up to date. Only it will happen faster or slower depending on the grid. In real life, scrolling is not blocked by UI updates which leads to whiteouts instead.
FPS
To measure FPS (frames per second) a utility class that utilizes requestAnimationFrame
is used. It measures the time taken to reach the scroll destination, counts the number of frames run and calculates FPS. Source redacted to save some space:
export class FPS {
static start() {
this.start = null;
this.frameCount = 0;
this.running = true;
requestAnimationFrame(this.frameCounter);
}
static stop() {
this.running = false;
const
elapsed = performance.now() - this.start,
fps = this.frameCount / (elapsed / 1000);
console.log(fps);
}
static frameCounter() {
const time = performance.now();
if (FPS.start === null) {
FPS.prevFrameTime = FPS.start = time;
} else {
FPS.frameCount++;
}
FPS.prevFrameTime = time;
if (FPS.running) {
requestAnimationFrame(FPS.frameCounter)
}
}
}
Usage:
FPS.start();
// actions...
FPS.stop();
Results
First up, initial rendering. Scroll down for the scroll performance results.
Initial Rendering
First the results for initial rendering, without locked/fixed/pinned columns:
As can be seen above, dhtmlX is the fastest for the initial rendering phase, with Bryntum as the clear runner-up. For all practical applications though, the difference between the best and the worst, in this case, is negligible. A real world application loading and displaying 10,000 records will spend much more time retrieving the data from the backend than the time it takes to display it.
Reviewing the results for initial rendering with locked/fixed/pinned columns we can see that the relative ranking is the same, but it takes a wee bit longer to render:
ExtJS renders pretty much slower in this case, but it is not noticeable unless you measure it. Still fast enough across the board.
Scrolling FPS
When it comes to FPS there are bigger differences between the grids. Lets first look at the scenario without locked/fixed/pinned columns:
Ag-grid, Bryntum and Ext JS are pretty much tied for the lead, averaging at around 60 frames per second which is as good as it gets! DevExtreme and dhtmlX lag behind at 38 and 25 FPS respectively.
In contrast to the initial rendering performance, these performance differences are easy to spot with the eye. Scrolling fast in a grid that has low FPS leads to whiteouts and poor user experience.
Now for the final round of results, we have the grids with locked/fixed/pinned columns. This case is especially interesting for anyone building or using Scheduler or Gantt components as such components always have a fixed left section with the tabular data.
Bryntum almost manages to maintain 60 frames per second, while Ag-grid and ExtJS are a few frames behind. Still very fast though, kudos to the developers of both libraries. DevExtreme and dhtmlX lag further behind, probably because of how locked sections are implemented in those grids.
Summing Up
This article is not comparing features of the grids, but if you care about performance as much as we do then this post might be helpful when picking which grid to use. Bryntum Grid performs good both initially and while scrolling, but as mentioned above the initial rendering time is good enough across the board. With that in mind it is hard to pick a single winner, so let's call this a tied 1st place between Ag-grid, Bryntum and Ext JS.
Disclaimer
All measurements were done in the same way for each grid and results are reported honestly, but I am a developer at Bryntum. For transparency, all the code used is available on GitHub. Feel free to try it out yourself: https://github.com/bryntum/grid-performance
Opinions expressed by DZone contributors are their own.
Comments