Notes About CSS

In my experience, engineering teams often neglect writing performant CSS. This neglecting has an adequate reason because there are better areas to apply developer resources that have much greater ROI. However for large applications, using certain techniques can provide noticeable performance gains. Everyone can celebrate that.



Luckily for us, this is a simple topic. And most likely you can still use your favourite preprocessor and postprocessor after reading this.



Creating The CSSOM

A tree is created called the CSSOM. It stands for the CSS Object Model. Here is a brief overview of how your browser creates the CSSOM:



  1. Read the raw bytes of the CSS or network and translate them into characters based on your system's encoding.
  2. Browser converts strings of characters into tokens.
  3. The browser transforms tokens into objects that hold properties and values.
  4. Objects are composed to form a tree based on their abstracted child, sibling, and parent relationship.

The product is a tree that the browser can use to determine the most applicable rules for any given DOM tree node.



The Importance Of Speed

Building the CSSOM is render blocking on the browser's main thread because it locks to finish this task. It's usually fast, but there are a couple of caveats. First of, if there is a lot of CSS, this can take longer than expected. Second, the browser builds the CSSOM each time you load a new page (shout out to the single page apps!).



So the point is: there are a couple of ways to improve this process regardless of what CSS church you belong to (Tachyons, OOCSS, ACSS, BEM, SMACSS or some other well thought out one). We are going to do this by reducing the amount of CSS we write, and making it easier for the browser to create the CSSOM.



Remove Unused Styles

Remove the duplicate properties, remove the empty rules, trim your CSS reset down, and remove any selectors that you are not using. This tactic is where you will find the most ROI on a large codebase.



/* divs are already blocks so delete this */div {  display: block;}
/* delete empty cases like this */.page {}.element {}span {}



Use a Flat Hierarchy

CSS selectors get evaluated from right to left, and for each selector, the browser checks the DOM tree until it reaches leftmost selector. Based on the complexity of selectors, this is a complexity for creating the render tree.



Google page speed recommendations consider descendant selectors, and child or adjacent selectors to be slow. Therefore when it is possible, try to keep all of your class selectors at the top level to keep evaluation fast.



Note: IDs are very performant in CSS, but this rule suggests that you should avoid styling ID in your CSS. I agree with this because each ID represents a unique instance of an element of your code, so its usage is counter intuitive towards reusability and modularity (debatable if you're using SASS mixins for reusability).



/* good practice */.popover {  opacity: 0;}
.popover--active { opacity: 1;}
/* also viable if using SASS preprocessor */.popover { opacity: 0;
&--active { opacity: 1; }}



Avoid Depth and Traversal

Since we know the number of selectors used is a performance bottleneck. Do not write selectors that will cause the traversal of more nodes before the browser applies a style to the element.



/* do not do this */.popover .popover-children .popover-children--active {}.element element-item--pureVersion span:last-child {}



Remove Overqualified Elements

Removing overqualified elements improves performance by eliminating the need to match unnecessary elements and reducing the bytes used in your CSS file.



/* avoid */figure.is-active {  background-color: red;}
/* avoid even more */.list li.is-hidden { opacity: 0;}



Careful With Universal Selectors

Since browsers evaluate selectors from right to left, a universal selector starts by matching every element in the document. Afterwards, it will attempt to match the next selector on the left. Since some developers enjoy the ease of universal selectors, I will not dismiss the validity of using a top level universal selector.



/* very bad */span .dog * {  color: red;}
/* valid but debatable */* { box-sizing: border-box;}



Avoid Selectors By Attribute

Writing selectors by attributes match all the elements first and then match the attribute itself. So it is redundant and relatively slower than using classes as selectors.

/* targets all inputs */[type="text"] {  border: 0;}
/* even more expensive after evaluating right to left */input .my-input [type="text"] { border: 0;}
/* can do the same thing */.my-input { border: 0;}



Appropriate CSS Properties

These extra lines are booby traps for future developers looking at your code, and these additional lines waste bytes and CSSOM building time.



display: inline;/* avoid: width, height, margin, float */
display: inline-block;/* avoid: float */
display: block;/* avoid: vertical-align */
display: table-*;/* avoid: margin, float; */



Shorthand Properties

You can often write multi-line CSS rules as a single rule to save bytes.



/* bad */margin-left: 16px;margin-right: 16px;margin-top: 16px;margin-bottom: 16px;
/* good */margin: 16px;



Zero Values

There is no difference between 0px, 0em, and 0% or any zero-value. The browser knows what to do if you omit the unit. Save your bytes here.



/* same */margin: 0px;margin: 0em;margin: 0%;margin: 0;



Conquer The World

Once you get into the habit of writing performant CSS, you'll be on your way to building performant web apps and websites that load in sub-seconds. If you are writing a CSS generator, following some of these rules will give you an edge.



Hey! My name is Jim and I love to build websites. You can learn more about me here.

To reply you need to sign in.