Why isn't content-visibility:auto working in this simple example?

Viewed 753

I've created two files, each with 100,000 div elements. The first is slow.html:

<div>0.8562158266079849</div>
<div>0.9135280673563708</div>
...
<div>0.5053808333117775</div>
<div>0.9188260452433614</div>

and the second is fast.html:

<style>
  div {
    content-visibility:auto;
    contain-intrinsic-size: 100vw 18.5px;
  }
</style>

<div>0.8562158266079849</div>
<div>0.9135280673563708</div>
...
<div>0.5053808333117775</div>
<div>0.9188260452433614</div>

As you can see, each div is just filled with a random decimal number. The only difference between slow.html and fast.html is that fast.html has that <style> block at the top.

slow.html takes about 2.7s to load:

slow.html page load performance pie graph

and fast.html takes about 17s to load:

fast.html page load performance pie graph

So content-visibility:auto has slowed down page load by quite a bit rather than speeding it up. Am I misunderstanding and/or misusing content-visibility? Is it not designed for the situation where I've got a large number of very simple elements?

My intended use-case is for helping speed up the rendering of tables of content where it's not worth it to implement virtual scrolling because they're just quick/scrappy/throwaway tools for internal analytics use.

I have tested on Chrome v89 and v90. Both have roughly the same load times.

Here's a one-liner to help you quickly replicate this:

new Array(50000).fill(0).map(_ =>`<div>${Math.random()}</div>`).join("\n")

Note that using content-visibility and contain-intrinsic-size has worked as expected (massively reduces rendering time) in my previous tests, but it strangely doesn't work in this minimal example.

1 Answers

It turns out (explained to me by a Chromium dev) that the overhead of adding an intersection observer to each of the 100k elements (which Chromium does for content-visibility:auto elements) is expensive, and so it's not really designed for such a large number of elements.

It's possible that browser developers will make their algorithms more efficient in the future, but currently the best approach if you've got a lot of elements is to nest them into blocks (perhaps 1000 rows per block) which themselves have content-visibility:auto:

<style>
  .block {
    content-visibility:auto;
    contain-intrinsic-size: 100vw 18500px;
  }
</style>

<div class="block">
  <div>0.8562158266079849</div>
  <div>0.9135280673563708</div>
  ...
  <div>0.5053808333117775</div>
  <div>0.9188260452433614</div>
</div>
<div class="block">
  <div>0.5053808333117775</div>
  <div>0.9188260452433614</div>
  ...
  <div>0.8562158266079849</div>
  <div>0.9135280673563708</div>
</div>
...

Notice that since the blocks have 1000 divs that are each 18.5px in height, I've specified 18500px as the contain-intrinsic-size height.

This has the added benefit of removing the layout cost of all of the divs off-screen blocks. The example in my original question only removed the rendering cost of the off-screen divs.

You can improve this further by nesting blocks within blocks (each with content-visibility:auto and a calculated contain-intrinsic-size height), such that only a very small number of elements need to be rendered and laid-out at any given time.

Related