The smallest room for improvement in user’s experience is an opportunity, when you are dealing with payments. Our payment page is a web experience, shared on Android, m-web/web. A poorly performing page can impact millions of Hotstar’s potential subscribers and will have revenue impact.
Problem: “Jank” is any stuttering or choppiness that a user experiences on the screen.
One of the metrics to measure it is called “Cumulative Layout Shift (CLS)”. In layman terms, CLS reveals if an already painted element changes its layout or position on the screen thereby re-triggering the browser’s layout stage causing stuttering and janks.
Our Payment page, CLS Score came out to be 0.4 which fell under Poor category
As per Google, CLS > 0.25 gives Janky experience to the users and is considered to be poor.
Good — CLS below 0.1
Needs improvement — CLS between 0.1 and 0.25,
Poor — CLS above 0.25
Solutions
Here are a few solves to reduce jank.
Aspect ratio and Images
Specify the intrinsic width and height of images while loading them. This pre-occupies the space on the page (width*height) even before the image has been downloaded from the server.
A similar concept is used for ad-slots on advertisement pages, this tells the browser that the image with x and y dimensions will be coming soon, so the layout is preserved initially.
Note :
Even if you specify the height in terms of “px” via classname, the browser will calculate width of the incoming image and allocate layout beforehand, as it already knows about aspect ratio via inline props. So in both cases, the browser pre-allocates the space for the image.
Defer Loading non-critical DOM Elements
Look closely at the footer text in the attached clip. The footer text got rendered on screen in the first place. Post that, the payment methods like Credit and Debit Card load, and the footer text gets dragged to the bottom. This is Layout Shift which is ugly.
Why does it happen ? React Fiber incrementally renders component or it can schedule renders based on priority. A component whose render is slow would not block the sibling component from rendering. Thus Footer text ends up appearing on screen even before the methods component gets rendered.
In order to avoid layout shifts, any element should be either rendered at the correct position on the page or should not be rendered till the intermediate elements arrive. Both choices can be made depending on the use case and tradeoffs.
We all have conditionally rendered a component based on a state. For e.g :
{ load && }
But here the challenge was to “show” a component A to User only after a Component B was already “shown”. A div element is ready or loaded only after all its children are loaded. To note, the onLoad prop for React div element is called after the Components wrapped inside it are loaded/ready on the actual DOM tree.
“React Renders” doesn’t mean “Renders” on Actual DOM Tree. So you cannot depend on React state to achieve this.
So, the idea is to use the onLoad property of div.
Avoiding Non composited animations
Animations that work on changing the properties like width, height, etc are non-composite. Non-composited animations perform worse because they force the browser to do more work ( main thread ).
If your code triggers the Layout stage then, the Paint and Composite steps need to run again. A non-composited animation is any animation that triggers one of the earlier steps in the rendering pipeline (Style, Layout, or Paint) whereas the Composite animations are done on compositor thread (reducing main thread usage)
Conclusion :1. box.style.transform = "translateX(200px)";
2. box.style.left = "200px";Option 1 is always better than Option 2
Bundle Analysis and Code-splitting
In International Payments, we only support Credit cards and Debit cards but we ended up including other UI Components in bundles like Netbanking, UPI, etc. This is an opportunity when we can lazily load the payment method components dynamically.
Preload Image
The collage image that you see is a noncritical resource for the payment page. Keeping it hidden till it is fully loaded by the browser does the trick for us. It prevents showing an ugly partial loaded image on slow networks.
After Changes — The CLS of payment page reduces to 0 🎯 and experience on real devices became very smooth
Note : This was majorly about improving the CLS metric. A performant webpage depends on other metrics too. One needs to benchmark what works.
This a taste for the class of problems we’re faced with at Disney+Hotstar. If you’d like to come work with us, please check out Tech Jobs.