Managing layout efficiently (HTML)

To render an app onscreen, the system must perform complex processing that applies the rules of HTML, CSS, and other specifications to the size and position of the elements in the DOM. This process is called a layout pass and can be very expensive. The performance impact varies significantly, depending on the size and complexity of the DOM and its associated styles. On average, they can take from a few milliseconds to several hundred milliseconds to execute.

To get the best performance for your app, let's look at ways to minimize the time it takes to performance a layout pass.

Batch API calls that trigger a layout pass

Several APIs can trigger a layout pass. These include window.getComputedStyle, offsetHeight, offsetWidth, scrollLeft, and scrollTop. Because these APIs force a layout pass to take place, be careful how you use them in your app.

One way to reduce the number of layout passes is to batch API calls that trigger a layout pass.

To see how to do this, let's take a look at a code snippet. In both of the examples here, we are adjusting the offsetHeight and offsetWidth of an element by 5.

First, take a look at a common but inefficient way to adjust the offsetHeight and offsetWidth:

// Don't use: inefficient code.
function updatePosition(){
// Calculate the layout of this element and retrieve its offsetHeight
  var oH = e.offsetHeight; 

// Set this element's offsetHeight to a new value
  e.offsetHeight = oH + 5; 

// Calculate the layout of this element again because it was changed by the  
// previous line, and then retrieve its offsetWidth
  var oW = e.offsetWidth; 

// Set this element's offsetWidth to a new value
  e.offsetWidth = oW + 5; 
}

// At some later point the Web platform will calculate layout again to take this change into account 
// and render element to the screen

The previous example triggers 3 layout passes. Now take a look at a better way to achieve the same result:

Function updatePosition(){
// Calculate the layout of this element and retrieve its offsetHeight
  var height  = e.offsetHeight + 5;  

// Because the previous line already did the layout calculation and no fields were changed, 
// this line will retrieve the offsetWidth from the previous line
  var width = e.offsetWidth + 5; 

//set this element's offsetWidth to a new value
  e.offsetWidth = height;

//set this element's offsetHeight to a new value
  e.offsetHeight = width;
}

// At some later point the system will calculate layout 
// again to take this change into account and render element to the screen

Even though the second example is only slightly different than the first, it triggers only 2 layout passes instead of 3, a 33% improvement over the first example. Because the second example didn't access a property after it changed it, the system didn't need to perform a layout pass to give us the value.

Create and initialize UI elements before attaching to the DOM

To avoid performing unnecessary formatting or layout updates on incomplete UI elements when updating the UI, create and finish initializing the UI elements before appending them to the live DOM. If you must attach an element to the live DOM before its ready (for example, to use some Windows Library for JavaScript controls, you must add incomplete elements to the DOM) set the element's style.display property to "none" to hide the elements. Use style.display instead of hiding the element in other ways, such as by setting style.visibility to "hidden" or style.opacity to "0". This is because display to "none" tells the app that the element takes up no space and can be ignored for layout calculations, but setting style.visibility to "hidden" or style.opacity to "0" does not allow the app to ignore the element for calculations.