How to optimize DOM performance by improving resource parsing times

BB

Reduce the performance impact associated with the browser reading files

Tasks associated with the browser reading a file, such as CSS or JS, will block the main thread. This will interrupt the DOM parser and also potentially delay rendering. In many cases, parsing can be deferred until later in the DOM Construction and rendering process. As a result, the page will becomes useful to users more quickly.

This module will focus on how to reduce the parsing time of a CSS or JS file.

Code Splitting

Text compression through file minification can slightly reduce the time it takes to parse a file. gzip compression enables up to an 85% reduction in file size.

However, the browser parses the uncompressed version of the file. The associated parsing tasks still encompass the original uncompressed file size and is not reduced by 85%.

As a result, it's important to limit how much unused code is added to a page. One way to do this is through code splitting.

As the size of a website scales and many unique components and layouts are used, it actually makes more sense to add more resource requests to enable code splitting.

Code splitting is the opposite of bundling.

// INPUT
/src/styles/pages/home.scss
/src/styles/pages/about_us.scss
/src/styles/pages/blog.scss
/src/styles/pages/blog-single.scss
/src/styles/pages/products.scss
/src/styles/pages/product-single.scss
/src/styles/pages/services.scss

// OUTPUT
/dist/styles/pages/home.min.css
/dist/styles/pages/about_us.min.css
/dist/styles/pages/blog.min.css
/dist/styles/pages/blog-single.min.css
/dist/styles/pages/products.min.css
/dist/styles/pages/product-single.min.css
/dist/styles/pages/services.min.css

Code splitting can still be combined with bundling, especially for resources that load on every page.

/src/styles/pages/home.scss
/src/components/hero.scss
/src/components/featured_products.scss
/src/components/featured_blog.scss

/src/styles/utilities/helper_classes.scss
/src/styles/components/navigation.scss
/src/styles/components/progress_bar.scss
/src/styles/components/footer.scss

// OUTPUT
/dist/styles/pages/home.min.css
/dist/styles/main.min.css


In this case, the number of network requests increases from 1 to 2, but the download and parsing time likely will decrease.

CSS Efficiency

When CSS files are parsed, the resulting rules contribute to the formation of the CSSOM. We can make the process more efficient by simplifying our CSS.

Limiting Rule Specificity

The CSSOM is a hierarchical tree. When CSS rules are deeply nested, the calculation time increases.

Highly specific selector:

body .about_section .paragraph_node #span_in_paragraph.span-modifier {
  font-size: 12px;
}

With less nesting:

.span-modifier {
  font-size: 12px;
}

Reducing Query Time

<div class="box">
  <p>I need a different font size</p>
</div>
.box p {
  font-size: 18px;
}

Instead, add a class to the paragraph node and target it directly:

<div class="box">
  <p class="box__text">I need a different font size</p>
</div>
.box__text {
  font-size: 18px;
}

Or with SASS/SCSS:

// Generates the same CSS as the above code.
.box {
  &__text {
    font-size: 18px;
  }
}

Furthermore, you should also reduce CSS calculations (where possible):

<div class="box">...</div>
<div class="box">...</div>
<div class="box">...</div>
<div class="box">...</div>
.box:last-of-type {
  background-color: green;
}

When you generate the HTML, assign a class for the last node.

// Loop through each node in a range...
<div class="box">...</div>
// if index == array.length-1
<div class="box--last_child">...</div>

Then target the class directly in your CSS.

.box--last_child {
  background-color: green;
}

Reducing Node Lookup Time

For both CSS and JS, the lookup (querying or matching) time of a node will increase as the complexity of the query increases.

For example, adding a custom-box-attribute attribute name with a custom-box-value name could be useful depending on your usecase.

<div id="custom_box_id" class="custom_box_class" custom-box-attribute='red'>...</div>
<div id="custom_box_id" class="custom_box_class" custom-box-attribute='green'>...</div>
<style>
/* The ^ indicates a regular expression */
  [custom-box-attribute^='green'] {
    background-color: green;
  }
</style>
<script>
// Find the node that matches a specific attribute name using
  var customBox = document.querySelector("[custom-box-attribute^='green']");
  // Return the value of custom-box-attribute
  var attributeValue = customBox.getAttribute("custom-box-attribute");
  console.log(attributeValue)
</script>

However, when looking this node up with either CSS or JS, it's fairly expensive to use attributes. If you examine the object representation of this HTML node, you can see how id, className, and classList all have dedicated properties with very simple values.

The attributes property, however, is an array of objects. When matching this selector based on an attribute name, the lookup has to iterate through each Object in the attributes array and find the correct Object name (custom-box-attribute). When you consider that this task will occur for each node in the document, this task is not really efficient.

Furthermore, this operation is further complicated if you have numerous selectors using this custom attribute name. If you lookup a node based on a custom attribute value, the operation takes even more time. If you have lots of nodes using this custom attribute name with different values, you have to complete this expensive lookup for even more nodes (instead of the query breaking earlier by only needing to check one node with that attribute name).

Finally, using a regular expression as a selector adds even more complexity to the key/value lookup time since the match must be computed.

We could refactor the above code this way:

<div class="custom_box custom_box--red" custom-box-attribute='red'>...</div>
<div id="green_box" class="custom_box custom_box--green" custom-box-attribute='green'>...</div>
<style>
  /* Use a className with a suffix/modifier that seperates its from other custom_box divs that might exist */
  .custom_box--green {
    background-color: green;
  }
</style>
<script>
  var customBox = document.getElementById("green_box");
  var attributeValue = customBox.getAttribute("custom-box-attribute");
</script>

In conclusion, it's most efficient to match a node using an ID since the value is a string and there are generally not many nodes that use the same ID value. Additionally, JavaScript includes a getElementById document method that returns the first matc (and usually there is one unique ID value used in the entire document on purpose). Secondary to this, a classList lookup is still fairly efficient since the lookup values are limited to an array of strings.