◆ content-visibility: auto にする
  ◆ 画面に表示される部分以外の計算をしないので高速になる
  ◆ 一気にスクロールすると画面が白かったりする
◆ 非表示部分のサイズを正しく確保するために contain-intrinsic-size が必要
  ◆ 要素自身に height など指定せず中身のサイズに任せてる場合
◆ position を使って要素外にはみ出すものを作るとはみ出た部分は表示されない

React などのライブラリを使っていると仮想スクロールが使われることって結構ありますよね
多くのデータがある場合に 親のコンテナ要素に 1 行の高さ×要素数分の height を指定して 中には画面に表示される範囲の要素だけを追加するというものです

高さが固定ならスクロール位置さえわかれば計算はできるので 複雑そうに見えて実はそこまで難しくはないものです
ですが毎回そういう仕組みを入れるのは面倒です

そういえば CSS でもレイアウト計算など他に影響を与えないことを伝えて最適化するためのプロパティもあったはずです
contain はいまいちよくわかってませんがもっと簡単に使える content-visibility がありました

content-visibility

content-visibility を auto にすれば 画面に表示されていない部分を表示せず仮想スクロールのようなことができます

重めの画面を作るページを用意しました
Update ボタンを押すと 1000 行 20 列のデータを表示します
各セルは「行,列:ランダム」形式の文字列を表示していて 押すたびランダム部分が変わります

<!doctype html>

<script type="module">
update.onclick = () => {
const fragment = document.createDocumentFragment()

const rows = 1000
const cols = 30

const rand = Math.random().toString(16).slice(2)

for (let r = 0; r < rows; r++) {
const row = document.createElement("div")
row.className = "row"
for (let c = 0; c < cols; c++) {
const cell = document.createElement("div")
cell.textContent = `${r},${c}:${rand}`
row.append(cell)
}
fragment.append(row)
}

container.replaceChildren(fragment)
}
</script>

<style>
.row {
display: flex;
}
.row > div {
flex: 0 0 100px;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

<div>
<button id="update">Update</button>
</div>
<div id="container"></div>

ボタンを押して画面が更新されるのに 1 秒以上かかります
.row のスタイルに↓を追加します

	content-visibility: auto;
contain-intrinsic-size: 100vw 24px;

プレビュー用

<!doctype html>

<script type="module">
update.onclick = () => {
const fragment = document.createDocumentFragment()

const rows = 1000
const cols = 30

const rand = Math.random().toString(16).slice(2)

for (let r = 0; r < rows; r++) {
const row = document.createElement("div")
row.className = "row"
for (let c = 0; c < cols; c++) {
const cell = document.createElement("div")
cell.textContent = `${r},${c}:${rand}`
row.append(cell)
}
fragment.append(row)
}

container.replaceChildren(fragment)
}
</script>

<style>
.row {
display: flex;
content-visibility: auto;
contain-intrinsic-size: 100vw 24px;
}
.row > div {
flex: 0 0 100px;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

<div>
<button id="update">Update</button>
</div>
<div id="container"></div>

これでボタンを押したときにかかる時間がかなり短くなりました
1 秒もかかっていません

click リスナの処理が終わってから画面の更新処理が行われて その間は JavaScript の処理が行われないので

container.replaceChildren(fragment)
console.time("udpate")
setTimeout(() => {
console.timeEnd("update")
}, 0)

で大体の時間を測れます

何度か測ってみたところ content-visibility なしだと 1 秒前後で ありだと 150~200ms でした
5~10 倍ほどの速度になっています

画面に表示する範囲になって初めて要素内の描画を行うので スクロールバーを急激に動かしたりすると未描画部分でほんの少しのあいだ画面が真っ白だったりします

contain-intrinsic-size

content-visibility と一緒につけた contain-intrinsic-size ですが 今回のような場合には必要になります
content-visibility によって非表示になったときにその要素をどんなサイズとして扱うかを設定します
1 つめが width で 2 つめが height の指定です

content-visibility で hidden になっても 対象の要素に width/height が指定されていれば そのサイズになります
指定されていない場合は中の要素のサイズになるのですが content-visibility で hidden 状態になると中身が計算されず サイズが 0 になります
その対処のために非表示時にどんなサイズとして扱うかを設定できます
width/height プロパティがあればそっちのほうが優先されます

devtools を使って画面に表示されてる要素と表示されてない要素のサイズを見てみると 表示されてない部分はこのプロパティで設定したサイズになってるはずです

今回は行を縦に並べてるので重要になるのは height です
何も指定しない 0 だとスクロール中に前後にある何百行がすべて高さ 0 で計算されるのでスクロールバーがおかしなことになります
スクロールするとあちこちジャンプしたり長さも変わったりです

各行の高さと同じ高さを指定すると自然な動きになります
0 以外なら多少ずれていてもそこまで変な挙動にはならないようでした

本来の高さより小さい高さにすると スクロールバーの掴む部分が大きくなります
下にスクロールすると その部分が読み込まれると少し上に戻される動きになります

逆に本来の高さより大きい高さにすると スクロールバーの掴む部分が小さくなります
下にスクロールすると その部分が読み込まれるとさらに下に進むので移動速度が速く感じます

こういう機能を使うほど行数が多いところだと 完全に揃えなくてもだいたいあってれば自然な動きになるので 行の高さを厳密に計算したりせず大体の高さを指定しておくでも大丈夫そうです

検索できる

仮想スクロールだと 画面に見えている分の要素しかツリー状に存在しないので検索できないデメリットがあります
ですが content-visibility だと hidden になっている部分でも要素は存在するので検索可能です

検索できないのは仮想スクロールの導入を妨げる大きな要因だったので その影響を受けず使えるこの方法はとても良さそうです

単純なレイアウトだと何もなくても十分速い

比較用に表示に時間がかかるものを作っていたときに思ったのですが 単純なレイアウトだと結構なデータ量になっても十分に速かったです
実際今回の例でも 重い方でも 1 秒ほどで描画できてますし

flex とか ellipsis とかは結構重たくなります
これらを外すと content-visibility なしでも 700ms くらいになったりで 便利な表示な分重たくなるのを実感できます

デメリット

高速化するし検索もできるしで content-visibility: auto にはデメリットはなさそうと思っていたら ありました
このプロパティが設定された要素は 独立して計算されることになるので 要素内に全画面に表示するダイアログや要素のエリアからはみ出るポップアップ等を含められません

position: fixed をつけてもウィンドウ全体ではなく その要素が基準になります
top: 0 と left: 0 で要素の左上が基準になります
また overflow: hidden はつけていないのに はみ出た部分は表示されません

確認用のページです
左右のボックスにマウスを乗せるとカーソルの右下にメッセージが表示されます
左側は content-visibility を設定しているのでメッセージが見切れています

<!doctype html>

<script type="module">
for (const ref of document.querySelectorAll(".ref")) {
const hover = ref.nextElementSibling
ref.onmouseenter = () => hover.hidden = false
ref.onmouseleave = () => hover.hidden = true
ref.onmousemove = (eve) => {
hover.style.left = eve.offsetX + 10 + "px"
hover.style.top = eve.offsetY + 10 + "px"
}
}
</script>

<style>
.container {
display: flex;
gap: 20px;
}

.item {
flex: 0 0 160px;
border: 4px solid gray;
position: relative;
}

.cvauto {
content-visibility: auto;
}

.hover {
position: absolute;
background: yellow;
width: 200px;
}
</style>

<div class="container">
<div class="item cvauto">
<div class="ref">テキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</div>
<div class="hover" hidden>てきすとてきすとてきすとてきすとてきすとてきすとてきすとてきすとてきすとてきすと</div>
</div>
<div class="item">
<div class="ref">テキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</div>
<div class="hover" hidden>てきすとてきすとてきすとてきすとてきすとてきすとてきすとてきすとてきすとてきすと</div>
</div>
</div>

対処するには content-visibility: auto をつけた要素からみ出る要素は body 直下などに配置する必要があります
ライブラリではそうなってるのもありますが 吹き出しのポップアップ系だとスクロールに付いてこなかったりというデメリットもあります