How to Write Better CSS with Performance in Mind
Editor’s note: This article is part of our Code Optimization series, where we take a look at how to optimize coding for better efficiency in a bid to be better coders.
In today’s post we will ponder over the code choices we can make in CSS for improved site performance. But, before we dive into those choices, let’s first take a brief, closer look at the webpage rendering workflow in order to focus on the problematic (performance-wise) areas that are solvable via CSS.
Here’s the rough flow of operations performed by the browser after DOM tree creation:
- Recalculate Style (and render tree creation). Browser computes the styles to be applied to the elements in the DOM tree. A render tree is later created while discarding the nodes (elements) from the DOM tree that aren’t to be rendered (elements with
display:none
) and those that are (pseudo-elements). - Layout (aka Reflow). Using the computed style from before, the browser calculates the position and geometry of each element on the page.
- Repaint. Once the layout is mapped, pixels are drawn to the screen.
- Composite Layers. During repainting, the painting might be done in different layers autonomously; those layers are then finally combined together.
Now let’s continue on to what we can do in the first three stages of the operation to write better-performing CSS codes.
1. Reduce Style Calculations
Like mentioned before, in the "Recalculate Style" stage the browser computes the styles to be applied to the elements. To do this, the browser first finds out all the selectors in the CSS that point to a given element node in the DOM tree. Then it goes through all the style rules in those selectors and decides which ones are to be actually applied to the element.
To avoid costly style calculations, reduce complex and deeply nested selectors so that it’s easier for the browser to figure out which element a selector is referring to. This reduces computational time.
Other ways to employ include reducing the number of style rules (where possible), removing unused CSS and avoiding redundancy & overrides, so that the browser doesn’t have to go through the same style again and again during style calculations.
Tip: To reduce style calculation cost, a reduction in DOM tree size is as equally effective as the reduction of style rules.
2. Reduce Reflows
Reflows or Layout changes in an element are very "expensive" processes, and they can be of an even bigger problem when the element that went through the layout changes has a significant amount of children (since Reflows cascade down the hierarchy).
“At Google, we test the speed of our web pages and applications in a variety of ways – and reflow is a key factor we consider when adding features to our UIs.” – Google Developers
Reflows are triggered by layout changes to an element, like changes to the geometric properties such as height or font size, the addition or removal of classes to elements, window resizing, activated :hover
, DOM changes by JavaScript, etc.
Just like in style calculation, to reduce Reflows, avoid complex selectors and deep DOM trees (again, this is to prevent excessive cascading of Reflows).
If you have to change the layout styles of a component in your page, target the styles of the element that is at the lowest in the hierarchy of elements that the component is made of. This is so that the layout changes doesn’t trigger (almost) any other Reflows.
If you’re animating an element that goes through layout changes, take it out of the page flow by absoutely positioning it, since Reflow in absolutely positioned elements won’t affect the rest of the elements on the page.
To summarise:
- Target elements that are lower in the DOM tree when making layout changes
- Choose absolutely positioned elements for layout changing animations
- Avoid animating layout properties whenever possible
3. Reduce Repaints
Repaint refers to the drawing of pixels on the screen, and is an expensive process just like Reflow. Repaints can be triggered by Reflows, page scroll, changes in properties like color, visibility and opacity.
“Some paint effects, such as box-shadow, can be expensive, especially if you are applying them in a transition where the browser has to calculate them in every frame.” – Mozilla Developer Network
To avoid frequent and huge repaints, use less of the properties that cause costly repaints like shadows.
If you’re animating properties of an element that can trigger Repaint directly or indirectly, it’ll be of great advantage if that element is in its own layer preventing its painting prcoess from affecting the rest of the page and triggering hardware acceleration. In hardware accelaration, the GPU will take up the task of performing the animation changes in the layer, saving the CPU extra work while speeding up the process.
“In a nutshell, Hardware Acceleration means that the Graphics Processing Unit (GPU) will assist your browser in rendering a page by doing some of the heavy lifting, instead of throwing it all onto the Central Processing Unit (CPU) to do.” – Sara Soueidan
In some browsers, opacity
(with a value of less than 1
) and transform
(value other than none
) are automatically promoted to new layers, and hardware acceleration is applied for animations and transitions. Preferring these properties for animations is thusly good.
To forcefully promote an element to new layer and go into hardware acceleration for animation, there are two techniques invovled:
- add
transform: translate3d(0, 0, 0);
to the element, tricking the browser into triggering the hardware acceleration for animations and transitions. - add the
will-change
property to the element, which informs the browser of the properties that are likely to change in the element in the future. Note: Sara Soueidan has an in-depth and super-helpful article on this in the Dev.Opera site.
To summarise:
- Avoid expensive styles that cause Repaints
- Seek layer promotion and hardware acceleration for hefty animations and transitions.
Take Note
(1) So up til now, we haven’t touched on CSS file size reduction. We have mentioned that reduction in style rules (and DOM elements) make a significant performance improvement because of how much the browser will have to work less on the process of computing the styles. As a consequence of this code reduction, writing better selectors and the deletion of unused CSS, the file size will automatically decrease.
(2) It’s also advisable to not make too many consequential changes to an element’s styles in JavaScript. Instead add a class to the element (using JavaScript) that holds the new styles to make these changes – this prevents unnecessary Reflows.
(3) You will want to avoid Layout Thrashing as well (forced synchronous Reflows) which arises due to the accessing and modifying of the Layout properties of elements using JavaScript. Read more about how this kills performance here.