Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save gopal1996/de0b218253fcf8c9634bd8029d14eaf4 to your computer and use it in GitHub Desktop.
Save gopal1996/de0b218253fcf8c9634bd8029d14eaf4 to your computer and use it in GitHub Desktop.

Recently I came to know about Reflow and Repaint. How it's affecting web performance. I am writing this post to give insights about reflow and repaint. Before Jumping into the topic, let's understand how the browser renders the website.

Group 1

  • When the user enters the URL, It will fetch the HTML source code from the server
  • Browser Parse the HTML source code and convert into the Tokens (<, TagName, Attribute, AttributeValue, >)
  • The Tokens will convert into the nodes and will construct the DOM Tree
  • The CSSOM Tree will generate from the CSS rules
  • The DOM and CSSOM tree will combine into the RenderTree
  • The RenderTree are constructed as below:
    • Start from the root of the dom tree and compute which elements are visible and their computed styles
    • RenderTree will ignore the not visible elements like (meta, script, link) and display:none
    • It will match the visible node to the appropriate CSSOM rules and apply them
  • Reflow: Calculate the position and size of each visible node
  • Repaint: now, the browser will paint the renderTree on the screen

Repaint and Reflow

  • The Repaint occurs when changes are made to the appearance of the elements that changes the visibility, but doesn't affect the layout
    • Eg: Visibility, background color, outline
  • Reflow means re-calculating the positions and geometries of elements in the document. The Reflow happens when changes are made to the elements, that affect the layout of the partial or whole page. The Reflow of the element will cause the subsequent reflow of all the child and ancestor elements in the DOM

Both Reflow and Repaints are an expensive operation

According to Opera, most reflows essentially cause the page to be re-rendered:

Reflows are very expensive in terms of performance, and is one of the main causes of slow DOM scripts, especially on devices with low > processing power, such as phones. In many cases, they are equivalent to laying out the entire page again.

Here is a snapshot of how the browser draws user interface on the screen.

Gecko Reflow Visualization - Wikipedia

What Causes the Reflows and Repaints

  • Adding, Removing, Updating the DOM nodes
  • Hiding DOM Element with display: none (both reflow and repaint will occur)
  • Hiding DOM Element with visibility: hidden (only repaint will occur, because no layout or position change)
  • Moving, animating a DOM node
  • Resizing the window
  • Changing the font style
  • Adding or removing Stylesheet
  • Script manipulating the DOM
var bstyle = document.body.style; // cache
 
bstyle.padding = "20px"; // reflow, repaint
bstyle.border = "10px solid red"; // another reflow and a repaint
 
bstyle.color = "blue"; // repaint only, no dimensions changed
bstyle.backgroundColor = "#fad"; // repaint
 
bstyle.fontSize = "2em"; // reflow, repaint
 
// new DOM element - reflow, repaint
document.body.appendChild(document.createTextNode('dude!'));

Minimizing repaints and reflows

The strategy to reduce the negative effects of reflows/repaints on the user experience is to simply have fewer reflows and repaints and fewer requests for style information, so the browser can optimize reflows. How to go about that?

  • Don't change individual styles, one by one. Best for sanity and maintainability is to change the class names, not the styles. If the styles are dynamic, edit the cssText property
// bad
var left = 10,
    top = 10;
el.style.left = left + "px";
el.style.top  = top  + "px";
 
// better 
el.className += " theclassname";
 
// or when top and left are calculated dynamically...
 
// better
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
  • Batch DOM Changes

    • Use a documentFragment to hold temp changes
    • Clone, update, replace the node
    • Hide the element with display: none (1 reflow, 1 repaint), add 100 changes, restore the display (total 2 reflow, 2 repaint)
  • Don't ask for computed styles repeatedly, cache them into the variable

    • Multiple reads/writes (like for the height property of an element)
      • Writes, then reads, from the DOM, multiple times causing document reflows.
      • Read(cached), write(invalidate layout), read(trigger layout).
      • To fix: read everything first then write everything.
  • Resources:

Chrome Tool Performance

Chrome provides a great tool that helps us to figure out what is going on with our code, how many reflows (layout) and repaint do we have, and more details about the memory, events, etc.

Bad code

var box1Height = document.getElementById('box1').clientHeight;
document.getElementById('box1').style.height = box1Height + 10 + 'px';

var box2Height = document.getElementById('box2').clientHeight;
document.getElementById('box2').style.height = box2Height + 10 + 'px';

var box3Height = document.getElementById('box3').clientHeight;
document.getElementById('box3').style.height = box3Height + 10 + 'px';

var box4Height = document.getElementById('box4').clientHeight;
document.getElementById('box4').style.height = box4Height + 10 + 'px';

var box5Height = document.getElementById('box5').clientHeight;
document.getElementById('box5').style.height = box5Height + 10 + 'px';

var box6Height = document.getElementById('box6').clientHeight;
document.getElementById('box6').style.height = box6Height + 10 + 'px';

reflow

Optimized Code

document.getElementById('box1').style.height = box1Height + 10 + 'px';
document.getElementById('box2').style.height = box2Height + 10 + 'px';
document.getElementById('box3').style.height = box3Height + 10 + 'px';
document.getElementById('box4').style.height = box4Height + 10 + 'px';
document.getElementById('box5').style.height = box5Height + 10 + 'px';
document.getElementById('box6').style.height = box6Height + 10 + 'px';


var box1Height = document.getElementById('box1').clientHeight;
var box2Height = document.getElementById('box2').clientHeight;
var box3Height = document.getElementById('box3').clientHeight;
var box4Height = document.getElementById('box4').clientHeight;
var box5Height = document.getElementById('box5').clientHeight;
var box6Height = document.getElementById('box6').clientHeight;

reflow-good

Resource:

@jcgentr
Copy link

jcgentr commented Jun 28, 2022

Shouldn't the Optimized Code section read:

var box1Height = document.getElementById('box1').clientHeight;
var box2Height = document.getElementById('box2').clientHeight;
var box3Height = document.getElementById('box3').clientHeight;
var box4Height = document.getElementById('box4').clientHeight;
var box5Height = document.getElementById('box5').clientHeight;
var box6Height = document.getElementById('box6').clientHeight;

document.getElementById('box1').style.height = box1Height + 10 + 'px';
document.getElementById('box2').style.height = box2Height + 10 + 'px';
document.getElementById('box3').style.height = box3Height + 10 + 'px';
document.getElementById('box4').style.height = box4Height + 10 + 'px';
document.getElementById('box5').style.height = box5Height + 10 + 'px';
document.getElementById('box6').style.height = box6Height + 10 + 'px';

?

@sanjarcode
Copy link

sanjarcode commented Oct 23, 2022

Is "changing classNames" really efficient instead of changing styles? (assuming your style change code is optimal, not sequential as you correctly demonstrated).
Because at the end of the day style or classes all make the same CSSOM tree.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment