When scrolling down, instead of shifting all of the element's props or contents up one and adding a new one at the bottom, we take the topmost element and move it to the bottom, then change that element's contents to that of the new item, leaving all of the other elements alone. That's the imperative description of it. Declaratively, we can do a little math to represent that transformation functionally in JSX. In the code, I'm using a flex box to do this because it's easier to fit on a slide, but transforming the individual list items is probably marginally better.
Basically, we split the element pool into two buckets, each with the same size as our element pool. The first bucket contains items before the next multiple of our pool size, and the second bucket contains those after. So if our pool size is 24, the first bucket will display items 0 through 23, and the second would display items from 24 to 47. The second bucket is always displayed after the first bucket, regardless of where it is in the DOM. We then assign each list item to the first bucket to start. Then when we need to move the element to the bottom because of scrolling, we make it display an item in the second bucket that corresponds to the index of that element in the pool.
So when we initially scroll down, the first element in our pool transitions from displaying an item in the first bucket to one in the second bucket, and will therefore display item 24 instead of item 0. Once item 24 leaves the viewport, it will display item 48. Then once all elements are displaying items in the second bucket, the second bucket becomes the first bucket, and a new second bucket is formed for the second however many items the pool can hold. Now we can see our element pool being treated as a ring buffer of sorts and only re-rendering one element at a time when scrolling. This brings our scrolling performance back down to 01. Adding this optimization yields another 30% performance improvement in our real-world example and ensures the performance remains consistent with large viewports, like on those on vertical monitors.
Finally, I want to talk about some of the broader limitations that come with virtual scrolling and some new web standards that aim to fix some of these issues. The fundamental issue with virtual scrolling that can't be fixed by just coding better is that we're not rendering all of the elements, so those un-rendered elements don't show up in control F searching or in the browser's accessibility tree. This can cause issues for people who need to use screen readers, for example, and it means that you always need to implement a search bar for lists that you want to be searchable.
There's a new CSS property called content visibility that came out of some talks about making virtual scrolling better with web standards. When this prop is set to auto, if the element is not in the viewport, selected, focused or on the top layer, the browser skips rendering it entirely. However, the element is still in the DOM and, unlike display none, it's still in the accessibility tree and can be selected or focused. The browser can skip rendering the content, but the content is still there for control F and for screen readers. The problem, though, is that you still have to construct a DOM tree with all of these elements on your initial load, which in the real world, unfortunately, is still prohibitively slow. I haven't had too much time to experiment with this, but given what I've seen, I don't see virtual scrolling being dethroned by this anytime soon.
That said, from what I understand, content visibility wasn't really intended to entirely supplant virtual scrolling, and it's much better suited for large pages with a few large sections that will render outside the viewport rather than lists with lots of small items. For that use case, it's the perfect tool for the job and I highly encourage using it. So, in conclusion, lists are really common in web development, and hopefully this talk has inspired you to get as much as you possibly can out of them. There's a lot of performance being left on the table with traditional scrolling, and you can often take basic virtual scrolling further as well. Sample code for the techniques discussed in the talk will be available here. So, thanks for putting up with lots of code on slides and lots of programmer art, and of course for watching my talk. And if there's time for questions, I'd be happy to take some.
Comments