83

I have a question on a big # of dom elmenets and performance.

Let's say I have 6000 dom elements on a page and the number of the elements can be increased as a user interact with the page (user scrolls to create a new dom element) like twitter.

To improve the performance of the page, I can think of only two things.

  1. set display to none to invisible items to avoid reflow
  2. remove invisible items from the dom then re-add them as needed.

Are they any other ways of improving a page with a lot of dom elements?

Moon
  • 22,195
  • 68
  • 188
  • 269
  • 3
    Setting display to none triggers reflow which is completely opposite of what you want if what you want is to avoid reflow. Not doing anything doesn't trigger reflow. Setting visibility to hidden will also not trigger reflow. However, not doing anything a much easier. – slebetman Sep 27 '12 at 03:21
  • 3
    You should also be aware that your window manager already removes invisible pixels from the screen to speed up UI interaction. Doing it yourself in javascript will likely slow things down instead of speeding things up. – slebetman Sep 27 '12 at 03:23
  • @slebetman // When I set display to none, it triggers reflow. That's true. but..when you scroll, it also triggers reflow and browser has to calculate layout for all the elements on the page. I thought setting display none to some of the items on the page would help to reduce the layout calculation time. – Moon Sep 27 '12 at 03:23
  • with your 2nd comment, I think setting display to none won't help. – Moon Sep 27 '12 at 03:25
  • No, reflow isn't triggered when you scroll. The browser draws all elements at once even below the scroll line. – slebetman Sep 27 '12 at 03:25
  • @slebetman // according to http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/ it sounds like it does.. – Moon Sep 27 '12 at 03:27
  • 1
    No it doesn't. You can check it out yourself by compiling firefox or chrome with debug on and running it through a profiler. Scrolling triggers repaints, all scrolling in all programs even Word or Notepad triggers repaints. Even moving the mouse pointer triggers repaints. But repaints are cheap - they've already been optimized since the late 1980s and are implemented by the OS/window manager itself rather than the program. Scrolling does not trigger reflow. – slebetman Sep 27 '12 at 03:30
  • @slebetman // thank you for the insight. I will check it! – Moon Sep 27 '12 at 03:31
  • 1
    @slebetman // ah..I got impression of scroll reflow from http://paulirish.com/2011/dom-html5-css3-performance/ video. The video lists that window.scrollBy causes reflow. I wonder why it does when the user's scroll doesn't. – Moon Sep 27 '12 at 03:39
  • This is the **ultimate** script performance-wise - http://airbnb.github.io/infinity/ – vsync Sep 22 '14 at 13:11
  • Also, great article on this - http://dannysu.com/2012/07/07/infinite-scroll-memory-optimization/ – vsync Sep 22 '14 at 14:01
  • @Moon I have posted a similar question http://stackoverflow.com/questions/33340901/how-do-i-get-dynamic-content-in-a-div-while-scrolling-and-still-keep-a-limit-on Please share any insight you may developed. – Lord Loh. Oct 26 '15 at 19:40

3 Answers3

105

We had to deal with a similar problem on FoldingText. As the document grew larger, more line elements and associated span elements were created. The browser engine just seemed to choke, and so a better solution needed to be found.

Here's what we did, may or may not be useful for your purposes:

Visualize the entire page as a long document, and the browser viewport as the lens for a specific part of the long document. You really only have to show the part within the lens.

So the first part is to calculate the visible view port. (This depends on how your elements are placed, absolute / fixed / default)

var top = document.scrollTop;
var width = window.innerWidth;
var height = window.innerHeight;

Some more resources to find a more cross-browser based viewport:

How to get the browser viewport dimensions?

Cross-browser method for detecting the scrollTop of the browser window

Second, you need a data structure to know which elements are visible in that area

We already had a balanced binary search tree in place for text editing, so we extended it to manage line heights too, so this part for us was relatively easy. I don't think you'll need a complex data structure for managing your element heights; a simple array or object might do fine. Just make sure you can query heights and dimensions easily on it. Now, how would you get the height data for all your elements. A very simple (but computationally expensive for large amounts of elements!)

var boundingRect = element.getBoundingClientRect()

I'm talking in terms of pure javascript, but if you're using jQuery $.offset, $.position, and methods listed here would be quite helpful.

Again, using a data structure is important only as a cache, but if you want, you could do it on the fly (though as I've stated these operations are expensive). Also, beware of changing css styles and calling these methods. These functions force redraw, so you'll see a performance issue.

Lastly, just replace the elements offscreen with a single, say <div> element with calculated height

  • Now, you have heights for all the elements stored in your Data structure, query all the elements that lie before the visible viewport.

  • Create a <div> with css height set (in pixels) to the sum of the element heights

  • Mark it with a class name so that you know its a filler div

  • Remove all the elements from the dom that this div covers

  • insert this newly created div instead

Repeat for elements that lie after the visible viewport.

Look for scroll and resize events. On each scroll, you will need to go back to your data structure, remove the filler divs, create elements that were previously removed from screen, and accordingly add new filler divs.

:) It's a long, complex method, but for large documents it increased our performance by a large margin.

tl;dr

I'm not sure I explained it properly, but the gist of this method is:

  • Know the vertical dimensions of your elements
  • Know the scrolled view port
  • Represent all off-screen elements with a single div (height equal to the sum of all element heights it covers for)
  • You will need two divs in total at any given time, one for elements above the visible viewport, one for elements below.
  • Keep track of the view port by listening for scroll and resize events. Recreate the divs and visible elements accordingly
starball
  • 20,030
  • 7
  • 43
  • 238
Mutahhir
  • 3,812
  • 3
  • 21
  • 26
  • // That makes sense! Thank you for your insight! – Moon Sep 27 '12 at 03:36
  • Can you give an example of a case where a complex data structure would be needed? – Joren Van Severen Jul 15 '13 at 14:23
  • 3
    Btw, I've found this to be the only way to handle lots of DOM elements with still a reasonable scroll experience. Also, fetching scrollTop in (jquery) scroll handler doesn't cause a redraw. In fact it never does, it only clears the queue of redraws and repaints so that last up to date scrollTop can be returned. My guess is that it already up to date at that the moment the scrollevent is handled. – Joren Van Severen Jul 15 '13 at 14:27
  • @JorenVanSeveren Hmm, I think you're right that querying scrollTop should never force redraw. I didn't mean to imply that in my answer, only that `getClientRects`, `getBoundingClientRect` and other querying css styles / dimensions of the element will force the browser to 'apply' any changes you've made to the elements and force a redraw. – Mutahhir Jul 16 '13 at 04:34
  • 1
    @JorenVanSeveren It depends on your need re: complex data structures. As I said, we used a balanced binary tree because we already had one in place. I don't think you'll need it for JUST managing the heights of the dom. – Mutahhir Jul 16 '13 at 04:41
  • @Mutahhir thank you, could you explain how you avoided reflow/relayout when user scrolled down and you removed the DOM nodes at the top? – Gene Vayngrib Oct 12 '13 at 00:38
  • @GeneVayngrib whenever you will remove elements from the dom and insert others, there will be a reflow pass by the browser. So, I don't think you can avoid that using this method. That being said, you can reduce this by not just showing one screenful but 3 screenfuls. That way for +/- 1 page scroll there won't be a reflow. – Mutahhir Oct 17 '13 at 05:28
  • Very nice trick with single replacement DIV with predefined dimensions. Very clever! – Robert Koritnik Nov 15 '13 at 17:47
  • But how would you know which elements are in the viewport if each element has it's own dynamic height, and sometimes more than one element appear in the same row...looping on a ton of DOM elements, checking if they are within the viewport is not efficient at all. This method is good if you know exactly how many items you have per row, and if they are all the same height – vsync Sep 10 '14 at 21:29
  • 9
    @Moon: This same technique outlined in this answer has been implemented in a form of a plugin. [ClusterizeJS](http://nexts.github.io/Clusterize.js/). Works exactly as expected and it also supports item parity if you need it. And it really works great! (FYI: I'm not a developer of it) – Robert Koritnik Jul 28 '15 at 14:54
  • @Mutahhir thanks for your insight, it have me e very clear idea to this complicated issue. I can see some light now :) – Altin Oct 23 '16 at 04:46
  • 8 years late to the party, but this was a fantastic tip. Works perfect – NegativeFriction Jul 14 '20 at 00:12
  • How do we know the vertical heights of elements before placing them in the DOM? – Sarath S Nair Mar 01 '22 at 07:20
29

No experience myself with this, but there are some great tips here: http://engineering.linkedin.com/linkedin-ipad-5-techniques-smooth-infinite-scrolling-html5

I had a look at Facebook and they don't seem to do anything in particular on Firefox. As you scroll down, the DOM elements at the top of the page don't change. Firefox's memory usage climbs to about 500 meg before Facebook doesn't allow you to scroll further.

Twitter appears to be the same as Facebook.

Google Maps is a different story - map tiles out of view are removed from the DOM (although not immediately).

cbp
  • 25,252
  • 29
  • 125
  • 205
  • 1
    // Thank you for taking time to investigate your find. I will do the same soon. – Moon Sep 27 '12 at 02:52
  • 2
    Would be really interesting to see how Pinterest does it. They seem to have figured it out as they load elements in dynamically as you scroll down and back up. They always seem to have a set number of Pins loaded on feed page at any given time. – alex Feb 20 '15 at 08:12
12

It's 2019. The question is really old, but I think it is still relevant and interesting and maybe something changed as of today, as we all now also tend to use React JS.

I noticed that Facebook's timeline seems to use clusters of content which is hidden with display: none !important as soon as the cluster goes out of view, so all the previously rendered elements of the DOM are kept in the DOM, it's just that those out of view are hidden with display: none !important. Also, the overall height of the hidden cluster is set to the parent div of the hidden cluster.

Here are some screenshots I've made:

enter image description here

enter image description here

enter image description here

As of 2019, what do you think about this approach? Also, for those who use React, how could it be implemented in React? It would be great to receive your opinions and thoughts regarding this tricky topic.

Thank you for the attention!

tonix
  • 6,671
  • 13
  • 75
  • 136