29

I have a page with a lot of vertical scrolling and thousands of DOM elements. For improving performance, I thought about setting display: none; to the content of the divs above and below the viewport, that is, the divs which are not visible (while keeping their heights, obviously):

enter image description here

In order to check if my idea makes any sense I searched SO and I found this question. According to the comments and to the accepted answer, the best strategy is do nothing, since display: none; triggers reflow and may have the opposite effect:

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.

However, there is a recent answer (which unfortunately seems more like a comment or even a question) that claims that display: none; is the current strategy used by sites like Facebook, where the vertical scroll is almost infinite.

It's worth mentioning that, unlike OP's description in that question, each visible div in my site is interactive: the user can click, drag, and do other stuff with the div's contents (which, I believe, makes the browser repainting the page).

Given all these information, my question is: does display: none; applied to the divs above/below the viewport improve performance or does it worsen performance? Or maybe it has no effect?

  • 10
    I can't really answer the question about performance, but note that `display: none` **doesn't** keep the heights of the elements. `display: none` takes the elements out of the flow entirely. `visibility: hidden` would make them not display but retain their position and dimensions in the layout. If Facebook is doing that, they must have an element that isn't `display: none` that's maintaining the height. But they may well be using `display: none` on a wrapper *inside* those, so that when reflow happens, the (probably complex) layout logic within those doesn't have to happen again. – T.J. Crowder Jul 03 '20 at 06:58
  • Thanks for your comment, but I already know it: that's precisely why I said in my question that I'd apply `display: none;` to the divs' **contents**, so I can keep the divs' height. –  Jul 04 '20 at 03:58
  • @Megapteranovaeangliae the strategy for infinite scroll is to remove DOM elements to lighten memory load, since for very large lists that tends to be the bottleneck – user120242 Jul 06 '20 at 03:41
  • fb's case is different, they just cap the amount on a page instead of trying to support unlimited scroll, probably on assumption that nobody needs to scroll through their feed for that much at once. fb uses display:none just because they don't want offscreen virtual scroll taking up space, and reflow doesn't matter, because they have to append it to the bottom anyways. allows a loading placeholder when you get to that point, which makes the experience slightly better because you get immediate feedback – user120242 Jul 06 '20 at 03:44
  • also the person you linked was analyzing the news feed, which is capped. not the profile feed, that will scroll infinitely. the analysis being not useful, because it doesn't address the infinite scrolling issue you seem to be asking about. if capped virtual scrolling is what you are asking about, then the answer is just that it doesn't matter because it is capped – user120242 Jul 06 '20 at 04:09
  • 1
    "display: none; triggers reflow" nope, not in itself, or at least not more than any other changes in the styles. In your scenario, since you'd box your content inside fixed sized containers, setting the content to display none will only affect this content area, and since it's set to display none, it will get short-circuited to "do nothing", where a visibility: hidden would still have to check all the inner nodes for a possible visibility: visible. Now, regarding the perfs, the only ones that can tell you for sure are your dev-tools -> test both, profile, keep the best on most used configs. – Kaiido Jul 06 '20 at 08:11
  • It worsens performance because you are loading objects to the dom and then not displaying them. Better to hold them back from loading in the first place with something called "Lazy Loading". Then whatever is in view loads; so long pages as the user scrolls content is loaded. – Nathaniel Flick Jul 06 '20 at 19:51
  • You shouldn’t do this anyway, it breaks browser find. – Ry- Jul 08 '20 at 03:32
  • If you are using React then `react-virtualized` would be a good solution: https://bvaughn.github.io/react-virtualized/#/components/Collection – Lucas D Jul 09 '20 at 03:51

5 Answers5

20

The "display: none" property of an Element removes that element from the document flow.

Redefining that element display property from none to any other dynamically, and vice versa, will again force the change in document flow.

Each time requiring a recalculation of all elements under the stream cascade for new rendering.

So yes, a "display: none" property applied to a nonzero dimensional and free flow or relatively positioned element, will be a costly operation and therefore will worsen the performance!

This will not be the case for say position: absolute or otherwise, removed elements form the natural and free document flow who's display property may be set to none and back without triggering e re-flow on the body of the document.


Now in your specific case [see edited graph] as you move/scroll down bringing the 'display: block' back to the following div will not cause a re-flow to the rest of the upper part of the document. So it is safe to make them displayable as you go. Therefore will not impact the page performance. Also display: none of tail elements as you move up, as this will free more display memory. And therefore may improve the performance. Which is never the case when adding or removing elements from and within the upper part of the HTML stream! enter image description here

Bekim Bacaj
  • 5,707
  • 2
  • 24
  • 26
  • I did not understand why the upper div would worsen the performance and the lower would not. If I set all divs position properties to absolute or fixed as you said, wouldn't it improve the performance regardless of the div being above or below the viewport div? – Vaibhav Chobisa Aug 11 '23 at 06:21
12

The answer is, like just about everything, it depends. I think you're going to have to benchmark it yourself to understand the specific situation. Here's how to run a "smoothness" benchmark since the perception of speed is likely more important than actual system performance for you.

As others have stated display:none leaves the DOM in memory. Typically the rendering is the expensive part but that's based on how many elements have to be rendered when things change. If the repaint operation is still having to check every element you may not see a huge performance increase. Here are some other options to consider.

Use Virtual DOM

This is why frameworks like React & Vue use a Virtual DOM. The goal is to take over the browser's job of deciding what to update and only making smaller changes.

Fully Add/Remove elements

You could replicate something similar by using Intersection Observer to figure out what's in/out of the viewport and actually add/subtract from the DOM instead of just relying on display:none alone since parsing javascript is generally more efficient than large paints.

Add GPU Acceleration

On the flip side, if the GPU is taking over rendering the paint might not be a performance suck but that's only on some devices. You can try it by adding transform: translate3d(0,0,0); to force GPU acceleration.

Give the Browser Hints

You may also see an improvement by utilizing CSS Will-Change attribute. One of the inputs is based on the content being outside the viewport. So will-change:scroll-position; on the elements.

CSS Content-Visibility (The bleeding edge)

The CSS Working group at W3C has the CSS containment module in draft form. The idea is to allow the developer to tell the browser what to paint and when. This includes paint & layout containment. content-visibility:auto is a super helpful property designed for just this type of problem. Here's more background.

Edit (April 2021) this is now available in Chrome 85+, Edge (Chromium) 85+, and Opera 71+. We're still waiting on Firefox support, but Can I Use puts it a 65% coverage.

It's worth a look as the demos I saw made a massive difference in performance and Lighthouse scores.

Bryce Howitson
  • 7,339
  • 18
  • 40
3

To add to the already posted answers.

The key takeaways from my testing:

  • setting an element to display: none; decreases ram usage
  • elements that are not displayed are not affected by layout shifts and therefore have no (or very little) performance cost in this regard
  • Firefox is way better at handling lots of elements (~x50)

Also try to minimize layout changes.

Here is my test setup:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            margin: 0;
        }
        

        .testEl {
            width: 100%;
            height: 10px;
        }

    </style>
</head>
<body>
    <main id="main"></main>
</body>

<script>
    // firefox Max
    // const elementCount = 12200000;

    // chrome Max
    const elementCount = 231000;

    const main = document.getElementById("main");

    const onlyShowFirst1000 = true;

    let _content = ""
    for (let i = 0; i < elementCount; i++) {
        _content += `<div class="testEl" style="background-color: hsl(${(Math.random() * 360)|0}, 100%, 50%); display: ${!onlyShowFirst1000 || i < 1000 ? "block" : "none"}"></div>`;
    }
    main.innerHTML = _content;

    const addOneEnd = () => {
        const newEl = document.createElement("div");
        newEl.classList.add("testEl");
        newEl.style.backgroundColor = `hsl(${(Math.random() * 360)|0}, 100%, 50%)`

        requestAnimationFrame(() => {
            main.appendChild(newEl);
        })
    };

    const addOneBeginning = () => {
        const newEl = document.createElement("div");
        newEl.classList.add("testEl");
        newEl.style.backgroundColor = `hsl(${(Math.random() * 360)|0}, 100%, 50%)`

        requestAnimationFrame(() => {
            main.insertBefore(newEl, main.firstChild);
        })
    };

    const loop = (front = true) => {
        front ? addOneBeginning() : addOneEnd();
        setTimeout(() => loop(front), 100);
    };
</script>
</html>

I create a lot of elements and have the option to only display the first 1000 of them using the onlyShowFirst1000 flag. when displaying all elements, firefox allowed up to ~12200000 elements (using 10gb of my RAM) and chrome up to ~231000.

Memory usage (at 231000 elements):

          +----------+----------+-------------+
          | false    | true     | reduction % |
+---------+----------+----------+-------------+
| Chrome  | 415,764k | 243,096k | 42%         |
+---------+----------+----------+-------------+
| Firefox | 169.9MB  | 105.7MB  | 38%         |
+---------+----------+----------+-------------+

Changing the display property of an element to or from none causes the area to be repainted, but the area of your element will usually be relatively small, therefore the performance cost will also be small. But depending on your layout, the display change might also cause a layout shift which could be quite costly since it would cause a big part of your page to repaint.

In the future (e.g. chrome 85) you will also be able to use the content-visibility property to tell the browser which elements dont have to be rendered.

Also, you set the browser to show repaints using the dev tools, for chrome open the rendering tab and check "Paint flashing".

Teiem
  • 1,329
  • 15
  • 32
2

Two tips for improving performance when you have thousands of DOM elements and they needs to be scrolled through, interact etc.

  1. Try to manage bindings provided by front end frameworks manually. Front end frameworks might need lot of additional processing for the simple data binding you need. They are good up to certain number of DOM elements. But if your case is special and exceeds the number of DOM elements in an average case, the way to go is to manually bind them considering the circumstance. This can certainly remove any lagging.

  2. Buffer the DOM elements in and around the view port. If your DOM elements are a representation of the data in a table(s), fetch them with a limit and only render what you fetched. The user scrolling action should make the fetching and rendering going upwards or downwards.

Just hiding the elements is definitely not going to solve your performance problem coming by having thousands of DOM elements. Even though you can't see them, they occupy the DOM tree and the memory too. Only the browser doesn't have to paint them.

Here are some articles:

https://codeburst.io/taming-huge-collections-of-dom-nodes-bebafdba332 https://areknawo.com/dom-performance-case-study/

Charlie
  • 22,886
  • 11
  • 59
  • 90
  • I really appreciate your answer, +1, it has a lot of useful information for my case and I'll definitely use it. However, in this question I was only looking for a clear-cut explanation regarding the relationship of `display: none;` and performance. –  Jul 07 '20 at 00:51
  • @Megapteranovaeangliae you won't have a definitive answer to that question, nothing prevents a browser to do exactly this switch to `display:none` when it determines the box of a container is out of the viewBox as part of their many optimizations (and IIIRC at least some recent browsers do this). So you'd probably not see much difference, or maybe better because it won't have to calculate when the boxes get off, or maybe worse, because your implementation will be less optimal than their. The advices in this answer are what you should focus on, let the optimizations to the browsers devs. – Kaiido Jul 07 '20 at 01:49
1

The strategy of "virtual scrolling" is remove the HTML element when it's out of viewport, this improve the performance because reduce the number of the elements into the dom and reduce the time for repaint/reflow all the document.

Display none don't reduce the size of the dom, just make the element not visible, like visible hidden, without occupies the visual space.

Display none don't improve the performance because the goal of virtual scrolling is reduce the number of the elements into the dom.

Make an element display none or remove an element, trigger a reflow, but with display none you worst the performance because you don't have the benefit of reduce the dom.

About performance, display none is just like visible hidden.

Google Lighthouse flags as bad performance pages with DOM trees that:

  • Have more than 1,500 nodes total
  • Have a depth greater than 32 nodes
  • Have a parent node with more than 60 child nodes

In general, look for ways to create DOM nodes only when needed, and destroy nodes when they're no longer needed.

The only one benefit of display none is: will not cause a repaint or reflow when change.

Source:

Simone Nigro
  • 4,717
  • 2
  • 37
  • 72
  • 1
    Any source about these claims? By experience, DOM operations really are nothing compared to reflow and painting, so avoid these **is a great perf improvement** in itself. – Kaiido Jul 06 '20 at 07:58
  • Also, `visibility:hidden` and `display:none` are two completely different situations, the former can allow to completely ignore all the content inside the node, while for the latter the browser still ha to check each and every inner element for a possible visibility:visible. – Kaiido Jul 06 '20 at 08:14
  • I really appreciate your answer, +1, just like the other answer it has a lots of useful information for my case and I'll definitely use it. However, as I comment in the other answer, in this question I was just looking for a clear-cut explanation regarding the relationship of `display: none;` and performance. –  Jul 07 '20 at 00:52
  • Thanks for the sources, now, did you check them? The first doesn't talk about CCSOM at all, and https://developers.google.com/speed/images/reflow-chart.png shows that reflow doesn't apply for `display: none`. – Kaiido Jul 07 '20 at 01:45
  • The first link talking about the performance benefit of reducing dom size. The second link talking about repaint/reflow. In the last sentence i say: "The only one benefit of display none is: will not cause a repaint or reflow when change.". What is wrong with my answer? – Simone Nigro Jul 07 '20 at 02:23
  • 1
    re:What is wrong with my answer? "reduce the time for repaint/reflow all the document","like visible hidden", "Display none don't improve the performance", "About performance, display none is just like visible hidden." And you contradict yourself -> "Make an element display none or remove an element, trigger a reflow" then "The only one benefit of display none is: will not cause a repaint or reflow when change." – Kaiido Jul 07 '20 at 05:07