Smooth animations and interactions in a webpage are critical for providing a natural-feeling experience. Non-performant ones feel awkward and slow.
Further down we will examine the impact of those animations in the rendering engines and some side effects taking place after. Those side effects are generally known as content reflows and repaints and could be triggered by animations.
Content reflows and repaints are expensive processes. They can be part of an even bigger problem when the element that went through the layout changes has a significant amount of children.
Alright, so what exactly are reflows and repaints?
To understand the concept fully, it’s best to start with a bit of a background on how the browser renders the page.
How browsers work
The process by which a web browser turns HTML, CSS and Javascript into a finished visual representation is quite complex and involves a bit of magic.
The Document Object Model (DOM) is a programming interface for HTML and XML documents. It represents the page so that programs can change the document structure, style, and content. The DOM represents the document as nodes and objects. That way, programming languages can connect to the page.
- MDN
The CSS Object Model (CSSOM) is a set of APIs allowing the manipulation of CSS from JavaScript. It is much like the DOM, but for the CSS rather than the HTML. It allows users to read and modify CSS style dynamically.
- MDN
The following steps outline the flow of operations performed by the browser after DOM and CSSOM tree creation:
- Calculate Styles Browser computes the styles to be applied to the elements in the DOM Tree and creates the Render Tree discarding elements that aren’t to be rendered (elements with
display: none
) and pseudo elements. - Layout (or Reflow) Using the computed styles from before, the browser calculates the position and geometry of each element on the page.
- Paint (or Repaint) Once the layout is mapped, pixels are drawn on screen.
- Composite Layers The painting might be done in different layers autonomously, which are then combined together.
The problem
Spotting what parts of a page require repaint may not be that obvious. Animations should always be tested.
Here at Skroutz, we faced an issue with animations triggered by color variations.
Products listing is one of our most popular pages. Some categories support color variations inside Product card. In order to show those variations we animate some elements when user hovers over the card.
You might think that this action affects only the Product card which have been hovered.
But wait… paint flashing showed something completely different.
The animation triggers multiple repaints (green flashing areas) on the following cards and on the rest of the page.
Those repaints occured because the browser is trying to do two things:
- Animate the content of the hovered card.
- Figure out whether any elements are overlapping the hovered card, by promoting all the elements into new layers.
Most of the times this is considered acceptable, but on slower clients, it is more expensive and could cause jank, especially, while the elements are being animated.
Even though someone would think that animating an element can only affect itself, it turns out that it causes repaints on different parts of the page.
The solution
What a great advantage it would be if an element belonging to its own layer could prevent its painting process from affecting the rest of the page and triggering hardware acceleration!
Hardware Acceleration is the process where GPU assists your browser in rendering a page by doing some of the heavy lifting, instead of throwing it all into the CPU to do.
- Wikipedia
There is a cool property, that “informs” the browser of an element is expected to change. In this way the browser set up optimizations before the element is ready to change. These kinds of optimizations can increase, dramatically the responsiveness of a page by doing expensive work before it is actually needed. This cool property is called will-change
but in our case study we will use transform: translateZ(0)
in order to avoid browser compatibility issues.
Both properties, can promote an element to a new stacking context.
As we mentioned before, the animation on a card, triggers multiple repaints, and that’s why it makes so much sense to think that each card have to be promoted into a new layer.
Lets see the result after applying that property on each Product card.
The results are impressive! Using only a single line of CSS we are able to reduce unnecessary repaints and improve the overall performance of our page, significantly.
Turning theory into numbers
The improvement of our performance, with the aforementioned changes, can be depicted in the table below.
Process | First test without transform | Second test with transform |
---|---|---|
Rendering | 30,3% | 32,5% |
Painting | 18,4% | 2,4% |
Rendering + Painting | 48,7% | 34,9% |
GPU | 5,6% | 1,9% |
GPU Memory | 20,8MB | 27,2MB |
Both painting time and gpu load have been reduced by 86,9% and 66% accordingly.
The drawbacks
There is a crack in everything…
However, there are some limitations of this technique.
By promoting an element into a new layer, a new stacking context is created. As a result, elements that overflow the promoted elements’ borders, are hidden when another promoted layer follows.
Let’s see an example below.
And it doesn’t stop there.
Each promoted layer binds extra space in memory. This could be an issue for clients with low-end hardware. Using that property “way too much”, could end up resulting in performance hits that could eventually crash your page.
Conclusion
transform: translateZ(0)
helped us write performance-optimized code.
Voltaire (or Uncle Ben 🤔) had said “With great power comes great responsibility” and transform: translateZ(0)
is one of those properties that should not be taken lightly and should be used wisely. You should always seek layer promotion and hardware acceleration for hefty animations and transitions.
You can leave a quick comment below with your experience or your opinion on rendering / painting performance.