Chatroom is slow? I know, I'll just use list virtualization!
Well now you have two problems.
List virtualization is hard, dear reader. So hard. “Pfft, I can build that in an hour,” I thought until I tried.
It's been 3 days. Last night, I dreamed scrollbars and mouse wheel events.
List virtualization is a performance improvement technique for large lists or tables. Whenever your UI becomes slow because you're rendering too much stuff, you can use list virtualization to make it fast again.
We've been hit with this problem recently at Yup. When our sessions get long, like hundreds of messages between tutor and student, tutors start complaining about the UI feeling sluggish.
They can't type, they can't talk, it's like teaching through a paper bag. Terrible.
We don't know why it became such a big problem right now, but 600 milliseconds to add a message to a chatroom is just too much. Needs to be fixed.
That's where list virtualization comes in.
Instead of keeping the whole hundreds-of-messages chatroom rendered, you render just 30 messages. Or 40, or 10. Whatever the number, you render a small subset of the messages.
And that gives you a performance boost. Fewer DOM nodes to deal with, faster rendering times. Especially on slow computers.
The problem may have become a problem because of the Spectre and Meltdown fixes that hit Intel processors with up to 30% slowdowns. When you're already using a slow $300 computer… yeah.
So, how do you virtualize a list?
Virtualizing a list is simple in theory. You maintain a window and move it around your list.
windowSize variable tells you how many nodes you're rendering, and a
windowIndex variable tells you where to start rendering. Then you
.splice your data array and render away.
I was doing it in Backbone, so it seemed super tricky. We had complex logic in place to append and remove and prepend messages to the list as necessary.
Think things like
- Find previous message with
- Manipulate with
- Find the correct scroll position
After a few hours of that approach, I gave up.
Screw DOM manipulation, it's 2018 and the DOM is fast. Re-render it.
To my surprise, throwing away the list and re-rendering for every message insertion or scroll event works friggin’ great. Like really seriously great. Even in Backbone where you get none of React's diffing magic.
I didn't run proper benchmarks, but on my 2017 MBP at home, re-rendering a 30-element list happens in 10 to 15 milliseconds.
- loop and append
That re-renders in just 10 milliseconds on my 15" MBP, 30 milliseconds on my 13" MBP. Both running RoR and sidekiq and webpack and the rest in the background.
13" is so much slower because it's just dual core. I'll run a proper benchmark for this soon because I'm curious.
My point is that re-rendering is fast and easy. You can totally get away with re-rendering on every mousewheel event if your
windowSize is small enough.
I now had a virtualized list. Worked great.
Except the UX is confusing. You get to the edge of the scrollbar and it just keeps going and going. Wat?
This strange scrolling UX is where things go belly up and life gets hard. Users don't expect to hit the edge of the scrollbar before the edge of the content, you see.
So I tried a couple things and haven't really figured this out. I think I'm on the right path, but I'm also starting to lose my mind.
Watch this gif. Scrolling up works great, but then you start scrolling down and everything goes topsy turvy.
The version in this gif tries to compensate for a few things.
- Uses an offset from the edge to start adding messages before you hit the edge
- Adjusts this offset to your scrolling speed
windowIndexdelta to your scrolling speed
Adding messages is basically just moving the
windowIndex. That part is easy.
But the mousewheel is a tricky beast. On my mouse, the smallest
deltaY is 4 pixels. That's okay.
deltaY I've seen is around 3000 pixels. In a single event call.
So that's what I've been dealing with. I wish I could just use react-virtualized, but I can't because "We'll have time to rewrite later". Can't come soon enough 😅
It's not as smooth as Slack, but it's good enough for now.
Wasn't that complicated after all. Just a bunch of maths with magic numbers.
I write articles with real insight into the career and skills of a modern software engineer. "Raw and honest from the heart!" as one reader described them. Fueled by lessons learned over 20 years of building production code for side-projects, small businesses, and hyper growth startups. Both successful and not.
Subscribe below 👇
Join Swizec's Newsletter and get insightful emails 💌 on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.
"Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"
Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.
Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.
Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.
Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev
Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization
Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog
By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️