High Performance HTML Tables with Lit and Virtual Scrolling
Web applications often need to handle large amounts of data, with HTML tables being a common method for presentation. However, traditional HTML tables can become slow and unresponsive when dealing with thousands of rows. Fortunately, with Lit, a modern, lightweight web component library, and the magic of virtual scrolling, you can create high performance tables with ease.
Setting the Table
Let's start with a basic LitElement named ui-element
:
import { html, LitElement } from 'lit';
import { customElement } from 'lit/decorators/custom-element.js';
import { virtualize } from '@lit-labs/virtualizer/virtualize.js';
import { styles } from './element.css.js';
@customElement('ui-element')
class Element extends LitElement {
static styles = [styles];
#items = new Array(10000).fill('').map((_i, n) => ({ column: `${n}-0`, column1: `${n}-1`, column2: `${n}-2`, column3: `${n}-3` }));
render() {
//...
}
}
Our custom element ui-element
has an array #items
containing 10,000 objects, with each object having four key-value pairs. That's a lot of data for an HTML table!
Rendering with Lit
Next, we use Lit's html
tagged template to create the table. We define a header using <thead>
, and for each key in our item object, we render a table header <th>
:
render() {
return html`
<table>
<thead>
<tr>
${Object.keys(this.#items[0]).map(i => html`<th>${i}</th>`)}
</tr>
</thead>
<tbody>
//...
</tbody>
</table>
`;
}
Virtual Scrolling
Here comes the tricky part. If we tried to render all 10,000 rows, our application would become sluggish. Virtual scrolling is a technique that only renders the items that are currently visible in the viewport.
We leverage @lit-labs/virtualizer
, a library that provides a function virtualize
to achieve this:
${virtualize({ scroller: true, items: this.#items, renderItem: (i: any) => html`
<tr>
${Object.keys(i).map(key => html`<td>${i[key]}</td>`)}
</tr>`
})}
We tell virtualize
to create a scrollable area with scroller: true
and pass our #items
array. The renderItem
function takes an item and returns a template literal to generate a row <tr>
with a cell <td>
for each key-value pair in the item.
import { html, LitElement } from 'lit';
import { customElement } from 'lit/decorators/custom-element.js';
import { virtualize } from '@lit-labs/virtualizer/virtualize.js';
import { styles } from './element.css.js';
@customElement('ui-element')
class Element extends LitElement {
static styles = [styles];
#items = new Array(10000).fill('').map((_i, n) => ({ column: `${n}-0`, column1: `${n}-1`, column2: `${n}-2`, column3: `${n}-3` }));
render() {
return html`
<table>
<thead>
<tr>
${Object.keys(this.#items[0]).map(i => html`<th>${i}</th>`)}
</tr>
</thead>
<tbody>
${virtualize({ scroller: true, items: this.#items, renderItem: (i: any) => html`
<tr>
${Object.keys(i).map(key => html`<td>${i[key]}</td>`)}
</tr>`
})}
</tbody>
</table>
`;
}
}
Conclusion
With Lit and virtual scrolling, you can efficiently manage and present large amounts of data in HTML tables. These techniques enable smooth user experiences, making your web application more responsive and performant. Check out the full working demo below!