Scrolling 40 000 elements with 60FPS
By David Kolinek
Socialbakers Builder is an app that lets you manage your profiles on social networks. You can control all of your pages from a single app, respond to people’s messages, boost your posts, monitor your competition, and much more. The app consists of feeds, where every feed represents something different. You can have one feed for Competition posts, one for Direct messages (DM), one for Sent posts, one for Listening… the list goes on. Our customers have many feeds in their workspace and every feed contains many posts. We call them wallitems. A wallitem is either a post or DM that is published on a social network (or is ready to be published). Every wallitem has a lot of operations you can choose from (Like, Retweet, Reply, change status, etc.). If you have, for example, 200 wallitems in every feed and 10 different feeds, you end up with 2000 wallitems. That’s a lot of HTML elements, and if styled wrong, they will send your browser performance into the ground.
It is a nuanced point, but the wallitem plays a critical part in our problem so I’ll elaborate on it a bit. Wallitem is the basic component for rendering content in our app. Text post, video content, photo with description, whole photo album, link to another website, direct message or even template in Outbox feed — all of these are rendered into wallitems. And as you would expect, you can do a lot of actions on every wallitem, from the basic actions like Retweet, Reply, Comment, Like etc. to the Builder internal actions like assigning posts to users, labeling, changing status, etc. For each of these actions, you need some button, which leads to many elements inside every wallitem.
Going back to the example above, having 2 000 wallitems doesn’t lead to having 2 000 DOM elements; it leads to having over 70 000 DOM elements, because every single wallitem consists on average of another 35 HTML elements. 2 000 wallitems is not out of the ordinary, but what if a client opens 20 feeds and the number doubles, or triples? We need to be prepared for everything and the app has to be fast even with 10 000 wallitems rendered into the DOM. And with so many elements in your DOM you start to notice that even small changes can have huge impact on performance. Let’s take a look at what we discovered.
The typical approach doesn’t apply here
If you have a button 21x21px large with a small icon inside (like the Comments icon in wallitem above) you would typically use pseudo elements for rendering the icon image so you can fit the icons inside your sprite. This is a great solution but instead of 1 HTML element you end up with 2. Having 10 icons styled like this in every wallitem leads to many unnecessary elements in your DOM. Instead we changed the icon to fit a grid, in this case a 21x21px grid, and we rendered the image as a background. It’s not rocket science, but it’s a simple solution that saves you thousands of DOM elements. Yes, this approach does come with some side effects: you are limited to the 21x21px grid, so you cannot resize the elements because another icon would appear in the background (due to the usage of sprite). So what do you do if a UX guru comes and wants you to put a number next to the icon showing how many internal comments there already are? We know we cannot use pseudo elements, only a background image. And we don’t want the text to be wrapped in another element, otherwise it would make no sense. Instead, we came up with a solution where the size of the element is set to 21x21px and overflow is visible. The icon has a left padding of 21px so the number renders right from the icon and also the right margin so the number doesn’t interact with another icon on the right.
Positioning 40 000 DOM elements: final touches
Reducing the DOM size wasn’t enough so we decided to take a look at the positioning of elements inside the wallitem. Because we have so many elements inside DOM we figured out that we can make a difference only by changing the positioning of elements. There is, however, no general approach on how to deal with it. Every element is unique and used differently so it’s up to the developer on how he or she positions them — the point being that if you have so many elements in play, you have come up with more solutions and measure which one is best.
When you are developing an application with such specific needs like Builder you need think outside the box. You might find that what works in the other 99% cases doesn’t apply in your case so you need to use a different approach. No matter how crazy an approach could sound at the beginning, you never know if a good idea could come out of it. This article is a little bit outdated. We had dealt with the issue early in 2015 but this blog post was written at the end of the year. Since then browsers came a long way and the rendering performance is much better. Especially in Firefox, Mozilla Dev team did such an amazing job that you are able to open “Original file” in Firefox and still reach about 58 FPS. Honestly, kudos to you guys, amazing work there. And one more thing from. The “Original file” was actually created now while writing this article. We created the before state only so we can show you the difference. The original code had more errors in it so the actual improvement in performance was even bigger at the time.