« Give up the jank! » or an overview of rendering performances in the browser
6 mar, 2018
Have you ever been on a website where your experience is being stepped on by slow responses of the different UI states or by a custom scroll that is constantly being interrupted by a full page animation and vice-versa? This incredibly irritating and frustrating behavior is referred to as jank, and it might just ruin the user experience on your website/app if it is not dealt with properly. Let’s dig in to find out when and how it happens but also let’s see how some simple solutions can overcome it and let us finally “give up the fun… jank”!
A janky experience on a website can be caused by long loading times and/or rendering problems. In this article we are going to focus on rendering issues.
Google has already done a great job on documenting and solutioning all those performance issues. This article is a summary of some of the great work they’ve done.
Device’s refresh rate, when?
Nowadays smartphones, laptops, computer screens… have a refresh rate of 60 images (or frames) per second. It means that for one second you spend looking at your smartphone, it has displayed 60 frames for you (how nice of it), that’s quite a lot right? Well that is why you can’t see each individual frame, this refresh rate is simply too fast for your eyes to follow. We call this phenomenon the persistence of vision.
How long does a frame last? Well the math is pretty straight forward:
1 (second) / 60 (frames) = 16.66 milliseconds
A frame lasts approximately 16 milliseconds. So when an animation is being played on your screen, like a circle that is moving from left to right for exemple, each 16 ms is rendered another frame displaying the circle a little bit more to the right than the previous frame. Easy peasy right? Here’s a picture representing this concept (but with a horse…).
But all this theory doesn’t explain when our jank appears… Patience we’re getting to it. So when an animation or a scroll occurs, the browser needs to calculate the next frame in a matter of 16ms in order to be synchronised with the device’s refresh rate (which is also 16ms if you’ve followed my flow) and display it on time. If the calculation takes more than that time it forces the frame to be on the screen more than 16ms seconds making the frame rate drop, resulting in sudden slowness in the UI… There’s our good old jank!
But wait it gets uglier! Those 16ms are just a vicious lie. When calculating a frame the browser has also other shit to do, which leaves it with only 10ms to do the work we ask it and render the frame. Pretty short Eh?!
Let’s now focus on the pipeline that the browser goes through in order to display a web page to the user.
The browser’s rendering areas, how ?
– style calculation: here the browser tries to figure out which css property is applied to which element based on the CSS selectors. The CSS rules are then applied.
– layout: this is where the browser figures out the sizes and positions of each of your html tags. It is a pretty heavy calculation to do because each element can have an impact on another one.
– paint: the area where the pixels are finally filled (texts, colors, images…) on different layers (just like on Photoshop)
– compositing: the process of drawing layers in the correct order, since there can be elements overlapping on each other.
Not everything you do will trigger those 5 areas, that’s why you need to know what a visual change will imply in order to prevent too heavy calculation. There are actually common scenarios that we can look into:
– JS > style > layout > paint > compositing: when a visual change occurs on a property that is bound to an element’s layout (width, height, position…), the browser has to go through all 5 areas of the pipeline.
– JS > style > paint > compositing: when you change a paint only property like color, background, shadows… the browser will not have to change the layout and will skip this area. The rendering is faster.
– JS > style > composite: from FAR the best scenario of all, and the one you should stick to the most. The browser will skip layout and paint and go straight to compositing. This has the cheapest cost on the rendering.
If you’d like to know which property triggers which area here’s a website that will tell you what you want to know.
The more calculation you’ll ask your browser to do for you the more risks you will have to end up with jank on your website.
What are some solutions?
Let’s look at some simple and concrete things we can do to avoid jank. These are all happening in the rendering pipeline since it’s the only thing we have little control over.
– Use window.requestAnimationFrame instead of setInterval or setTimeout.
Optimize your CSS
CSS also triggers visual changes : during keyframe animations, transitions or when you add/remove classes from the DOM that have different css properties. In any case you can follow some simple rules to avoid heavy calculations:
– Reduce selector complexity by using a class centric solution : BEM.
– Avoid changing layout properties: do NOT animate width, height, padding, margin, top, right, left… because this will trigger a reflow of the whole document and force the browser to go through the 5 areas of the rendering pipeline.
– Avoid animating paint properties as they are often expensive.
– Use new layout properties like css-grids or flexbox instead of older ones like float where you can.
– Use properties like will-change and translate-z to animate.
To sum up
So… What we’ve briefly explained in this article is what is jank and also how and where it happens. Then we’ve looked at some simple and concrete ways to avoid this problem as much as possible. To my mind, the good practices we often forget about are the RIGHT CSS properties to animate. This is something that front-end developers should know but also web designers and UX designers as it can have an impact on your user experience and on the user interface you are building. If you design a page with… let’s say… a ton of accordions you must know that most of the time accordion components are opened by toggling back and forth the height of the element. We learned here that this is not the best property, performance wise, to trigger. I’m not saying we shouldn’t use such components, I’m just saying that before we do we should weight the pros and cons on every level : UX, UI and front-end performances, and for that to happen we need to know how it works under the hood… thus, this article!
We are so so so sorry to end this article with a GIF of “give up the funk” performed by Glee.
Our sincerest apologies to the entire web community for this obvious lack of taste 😉
If you like to animate with code or articles about rendering check out these awesome books and links:
Some great animation libraries: