Rendering thousands of items in a browser list

Over at Keylime Toolbox, we have a feature that lets you test filter patterns against your query data. To make it “fast” we limit the data to the most recent day of data. But this can sometimes be 50,000 or more queries. So when rendering all those into a list (with some styling, of course), it would make the browser unresponsive for a time and sometimes crash it.


After hours of debugging and investigating options, I finally fixed this by limiting the number we render to start with and then adding “infinite scroll” to the lists to add more items as you scroll.

I looked around for existing solutions and found Marc Grabanski’s post from about how they made their lists faster using on-the-fly rendering of items. I tried to do something like that but I couldn’t even get the basic lists to render. When I added a list with 50,000 empty, non-styled items to the page it killed the page. From my investigation with Chrome profiler this appears to be something in our webshims library that’s firing a resize event on every DOM change. You can imagine why the browser can’t keep up.

My solution was to only render one hundred items in each list to start. Then, when you scroll to the bottom of the list, it adds more items. It turned out to be somewhat straightforward, so I’ll share some example code.

We use an AJAX call to get the data and an event on return. The success event looks something like this. We load all the data into an instance variable. Then render the framework for holding the data (the match_results template). Then we render the first 100 items the list. Finally I bind an event to the scroll for the lists.

  updateResultsWith: (data)->;
    @matchData = data

    results = JST['match_results'] data: @matchData

    @$('#results_lists ul').on('scroll', @onResultsListScroll.bind(@))

The renderMatches method adds the next 100 items to the list. I’ll show that in a minute. The template is pretty basic and could be as simple as a ul.


Now the fun comes in the scroll event. When the scroll hits the end of the page (using a hint from this SO post), we add more content to the list.

  onResultsListScroll: (event) ->
    $target = $(
    if $target.scrollTop() + $target.innerHeight() >= - 20

Because we’re waiting until the end of the scroll, we don’t have to mess with _.debounce or requestAnimationFrame like Marc did in the post I mentioned on top.

The magic of all this comes together by appending the next 100 items to the list whenever we call renderMatches. It turns out that’s actually pretty straightforward when I use JST to mix some coffescript into my templates.

  renderMatches: ->
    count = @$('#matches').find('li').length
    results = JST['list_items'] data: @data, offset: count

The list_items template could be as simple as this:

- for query in @data[@offset..@offset + 100]
    %li= query.string

Some thoughts on this.

First, I realize that eventually, you could have 50,000 items in the list again and that would slow down the page and the rendering and maybe kill the page. But the user has to scroll through them which I don’t actually believe that anyone will do. In testing, I got up to 20,000 items on the page and didn’t crash the browser (although my laptop got pretty hot), so perhaps this batch-loading addresses the original problem.

Second, by only rendering the first 100 items, I realize that we’ve lots the ability to Ctrl-F search for specific values. I don’t know if that’s a use-case so will talk to our customers about that and adapt if needed.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s