loading...

My eyes! or How to [get, avoid] Forced Synchronous Layouts

victormagarlamov profile image Victor Magarlamov ・2 min read

Manipulating the DOM in JavaScript allows us to do very interesting and impressive stuffs. This is a powerful ability, but, as you know, a powerful ability is often a double-edged sword. On the one hand we can have “Wow! Cosmically 🤩 cool!”. But on the other hand we can have “What's with the jank 🤢 on this page?!”

jank

  1. choppy performance
  2. discontinuos, surprising experience

Rash manipulating the DOM can lead to performance problems. One of them is layout thrashing or Forced Synchronous Layouts.

Each time we change the top, left, width, height and other geometric properties of the DOM element, the current layout becomes invalid and needs to be updated.

Typically, an update (or reflow) occurs at the end of the current frame. But we can force this process. If we ask for any geometric data, the browser will have to immediately update the layout.

There is one excellent example in the Google Web Fundamentals repository. Visit this page, follow all instructions and then analyze the performance record. You will see many purple rectangles labeled “Layout” and marked with a red triangle in the upper right corner. This mark is a warning that executed code leads to a forced reflow. And this significantly reduces FPS (the number of frames that the browser can draw in one second). In this case, we have much less than 60 frames per second, which is optimal for smooth animation.

Let's look at the code to understand how we can get the same effect.

for (var m = 0; m < movers.length; m++) {
   movers[m].style.left = ((Math.sin(movers[m].offsetTop + timestamp / 1000) + 1) * 500) + 'px';
   // movers[m].style.left = ((Math.sin(m + timestamp/1000)+1) * 500) + 'px';
}

At each iteration, we ask the offsetTop property of the movers[m] element. Due to the fact that the DOM has been changed and the layout is invalid, the browser needs to recalculate the layout in order to return the current position of the element. At each iteration! Think about it.

An another example.

const el = document.getElementById(element);
const container = document.getElementById(container);

if (el.offsetLeft > container.offsetWidth) {
  el.style.left = 0;
}

if (el.offsetTop > container.offsetHeight) {
  el.style.top = 0;
}

First, we request offsetWidth and get the value from the current layout. Then we change the left property and invalidate the layout. And we get Forced Synchronous Layouts when we request the offsetHeight property!

But it will be much better to get the width and height of the container in the first step, and then perform a comparison and other things.

Useful links

What forces layout / reflow by Paul Irish
Web performance for the curious by Ilya Grigorik

Discussion

markdown guide