【実務・中級編】大規模リストのレンダリングパフォーマンス – HTML実践ガイド

数千件のリストを「力技」でレンダリングしていませんか?DOM負荷を制する仮想スクロールの極意

現場でたまに見かけるんですよね。APIから取得した数千件のデータを、そのまま`map`関数で`

    `に流し込んで、「なんだか画面がカクつく」「スクロールが重い」と首を傾げている光景。

    結論から言います。ブラウザは、数千個のDOMノードを同時に扱うようには設計されていません。

    今回は、リストレンダリングのパフォーマンス限界を突破するための「仮想スクロール(Virtual Scrolling)」について、現場のリアルな視点から解説します。

    —

    なぜ、数千個の` `がブラウザを殺すのか

    ブラウザのレンダリングエンジンにとって、最もコストが高い作業の一つが「レイアウト(Reflow)」と「ペイント」です。

    数千件のリスト項目をDOMに展開すると、ブラウザはそれら全てのサイズを計算し、配置を確定させ、描画する必要があります。たとえ画面外にある見えない要素であっても、DOMツリーの中に存在している限り、ブラウザはリソースを消費し続けます。

    これを解決するのが仮想スクロールの考え方です。

    仮想スクロールの仕組み

    概念はシンプルです。「今、ユーザーが見ている画面範囲(Viewport)に必要な分だけDOMを生成し、スクロールに合わせてDOMを使い回す(リサイクルする)」という手法です。

    1. 全データ量は保持しておく。
    2. 表示領域の高さと1アイテムの高さから、今表示すべきインデックスの範囲を計算。
    3. 必要なDOMだけを生成する。
    4. スクロールイベントに応じて、DOMの中身を動的に差し替える。

    —

    実装の肝:Intersection Observer と スクロール位置の制御

    ライブラリなしでゼロから実装する場合、`scroll`イベントを直接監視するとメインスレッドが悲鳴を上げます。ここは`Intersection Observer API`を使うのが現代の定石です。

    しかし、最も「泥臭い」ポイントは「スクロールバーをどう見せるか」です。DOMを間引くと、本来のリスト全体の高さが失われ、スクロールバーが極端に短くなる(あるいは消える)問題が発生します。

    解決策は、「巨大な空のコンテナを用意して、スクロールバーの高さだけを担保する」こと。これが賢いやり方です。

    —

    そのまま使える!仮想スクロールのミニマル実装例

    ここでは、ReactのフックとCSSを組み合わせた、概念を理解するための最小構成コードを紹介します。

    import React, { useState, useMemo, useRef } from ‘react’;

    // 1件あたりのアイテム高さ(固定であることが前提のシンプルな実装)
    const ITEM_HEIGHT = 50;
    const VIEWPORT_HEIGHT = 500;

    export const VirtualList = ({ items }) => {
    const [scrollTop, setScrollTop] = useState(0);
    const containerRef = useRef(null);

    // 表示可能な範囲の計算
    const startIndex = Math.floor(scrollTop / ITEM_HEIGHT);
    const endIndex = Math.min(items.length – 1, Math.floor((scrollTop + VIEWPORT_HEIGHT) / ITEM_HEIGHT));

    // 表示すべきデータだけを切り出す
    const visibleItems = useMemo(() => {
    return items.slice(startIndex, endIndex + 1).map((item, index) => ({
    …item,
    // 絶対配置用のトップ位置を計算
    top: (startIndex + index) ITEM_HEIGHT,
    }));
    }, [items, startIndex, endIndex]);

    return (

    setScrollTop(e.currentTarget.scrollTop)}
    >
    {/ 全体の高さを保つためのダミー要素 /}

    {/ 実際に描画されるリスト /}
    {visibleItems.map((item) => (


    {item.content}

    ))}

    );
    };

    —

    現場で戦うためのアドバイス

    上記のコードは学習用としては最適ですが、実務で採用する場合は以下の点に注意してください。

    • アイテム高さが可変の場合:

    `ITEM_HEIGHT`が固定でないと、オフセット計算が地獄になります。その場合は、`ResizeObserver`で各アイテムの高さを計測し、キャッシュする仕組みが必要になります。

    • ライブラリの活用を恐れない:

    正直なところ、仮想スクロールの完全な実装はエッジケース(高速スクロール時のチラつき、キーボード操作、アクセシビリティなど)が多く、車輪の再発明になりがちです。`react-window`や`tanstack-virtual`のような実績あるライブラリをまず検討しましょう。

    • 「本当に必要なのか?」を疑う:

    数千件のリストを表示するUI自体が、ユーザー体験として正しいのか?という視点も忘れないでください。検索やフィルタリングでリストを絞り込むのが先決ではありませんか?

    まとめ

    仮想スクロールは、DOMの負荷を制御するための強力な武器です。しかし、実装難易度もそれなりに高い。まずは「ブラウザに何をさせ、何をさせないか」というレンダリングの最適化思想を理解することが、シニアへの第一歩です。

    明日からの開発で、重いリストに出会ったら「これはDOMを減らせるか?」と自問自答してみてください。その一歩が、あなたのフロントエンド・パフォーマンスを劇的に変えるはずです。

コメント

タイトルとURLをコピーしました