Avoiding HTML document reflows
Asked Answered
U

3

7

I have several hundred "row" elements like this:

<div class='row'>
  <div class='cell'></div>
  <div class='cell'></div>
  <div class='cell'></div>
</div>

I need to get their clientHeight after they have been rendered on the page already. I know that the "clientHeight" property forces a reflow and this kills my performance since there are so many of them. However - they are already rendered and I know that their size doesn't change between the time they are rendered and the time that I query for their height.

Is there a way to tell the browser to NOT reflow when querying for the height ? Moreover - the webkit inspector says:

Layout tree size  5901
Layout scope      Whole document

And the divs are within an absolutely positioned ancestor - shouldn't only the absolutely positioned element be reflowed?

Edit:

So the answers provided are correct. I was in fact dirtying the layout because I had this loop:

rows.each(function(){
  $(this).css('height', this.clientHeight);
});

Changing the code to this fixed the problem:

var heights = rows.map(function(){
  return this.clientHeight;
});
rows.each(function(){
  $(this).css('height', heights.shift());
});
Uretic answered 6/11, 2013 at 15:16 Comment(2)
One suggestion. If you can make your div row as static in the begining and get innerHieght of it and then change it to absolute. Or adding and removing class you can calculate your total hieght of your nodes.Psychasthenia
Why are you using div to simulate a table instead of using a table?Inexperience
C
6

The clientHeight property doesn't force a reflow, at least not by itself. You need two things to trigger a reflow:

  1. dirty the current layout (set a layout property like width or height)
  2. query a layout property (clientHeight for example)

You can easily batch calls to set the width or height and no reflow will happen until the JS context relinquishes control. You can easily batch queries for layout properties and no reflow will happen if the current layout is not dirty.

The problem appears when you do 1 and 2 in succession. The reflow is forced in order to satisfy your query with accurate information.

To my knowledge you are correct that having an absolutely positioned ancestor should limit the reflow from "spreading" all over the document (the parent of the absolutely positioned ancestor will not reflow). All the items inside the absolutely positioned ancestor will still reflow!

As for a solution to your problem yes there is. Set a caching mechanism, like this (pseudocode):

forEachDiv(function(div) {
  div.getClientHeight = function(){
    return div.cachedClientHeight = div.cachedClientHeight || div.clientHeight;
  }
});

You can then use div.getClientHeight() instead of div.clientHeight as many times as you want and only the first usage will trigger a reflow (if dirty).

Obviously, keeping in line with the first suggestion, you can also artificially query the clientHeight for all the divs in one batch ( = one reflow) and you have no more problems afterwards.

// cache the client height for all divs now, to avoid reflows later
forEachDiv(function(div) {
  div.getClientHeight(); 
});
Cota answered 6/11, 2013 at 15:33 Comment(3)
This is correct. Thank you. In my loop I was doing: element.style.height = element.clientHeight. If I first fetch all the heights, and then set them in another loop, there are no unnecessary reflows.Uretic
What is div.getClientHeight()? Did you mean div.getBoundingClientRect()?Ppm
@AlejandroIglesias It is a custom function that caches the height to avoid forced reflows.Cota
L
31

Reflow is queued whenever the DOM changes but only executed when either:

  1. There is no more javascript to process (end of script) or

  2. When you query for a calculated value that must be rendered such as clientHeight.

So, to avoid reflows, you must follow the following rules

  • Don't query the DOM while you're changing it and

  • Don't change the DOM when you want to query it

In practice this means that you should group all your query operations at the end of the script after you've done with modifying the DOM. Doing this will only reflow once for the thousands of elements instead of thousands of times for each element.

Linseylinseywoolsey answered 6/11, 2013 at 15:29 Comment(1)
Nice answer up voted. Can you provide some javascript example? For beginners please.Psychasthenia
C
6

The clientHeight property doesn't force a reflow, at least not by itself. You need two things to trigger a reflow:

  1. dirty the current layout (set a layout property like width or height)
  2. query a layout property (clientHeight for example)

You can easily batch calls to set the width or height and no reflow will happen until the JS context relinquishes control. You can easily batch queries for layout properties and no reflow will happen if the current layout is not dirty.

The problem appears when you do 1 and 2 in succession. The reflow is forced in order to satisfy your query with accurate information.

To my knowledge you are correct that having an absolutely positioned ancestor should limit the reflow from "spreading" all over the document (the parent of the absolutely positioned ancestor will not reflow). All the items inside the absolutely positioned ancestor will still reflow!

As for a solution to your problem yes there is. Set a caching mechanism, like this (pseudocode):

forEachDiv(function(div) {
  div.getClientHeight = function(){
    return div.cachedClientHeight = div.cachedClientHeight || div.clientHeight;
  }
});

You can then use div.getClientHeight() instead of div.clientHeight as many times as you want and only the first usage will trigger a reflow (if dirty).

Obviously, keeping in line with the first suggestion, you can also artificially query the clientHeight for all the divs in one batch ( = one reflow) and you have no more problems afterwards.

// cache the client height for all divs now, to avoid reflows later
forEachDiv(function(div) {
  div.getClientHeight(); 
});
Cota answered 6/11, 2013 at 15:33 Comment(3)
This is correct. Thank you. In my loop I was doing: element.style.height = element.clientHeight. If I first fetch all the heights, and then set them in another loop, there are no unnecessary reflows.Uretic
What is div.getClientHeight()? Did you mean div.getBoundingClientRect()?Ppm
@AlejandroIglesias It is a custom function that caches the height to avoid forced reflows.Cota
C
1

This is a bit of an old question but I stumbled upon it when researching the issue and so I thought I would share what I found during my research for others.

There is a pretty good library which helps you avoiding forced reflows, especially on larger applications: https://github.com/wilsonpage/fastdom.

The idea of the library is to group reads and writes into separate phases.

Chrischrism answered 10/10, 2018 at 6:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.