代表的な面接トピック

フロントエンド面接:高速でアクセシブルな仮想化リストをどのように構築するか?

フロントエンド難しい
Offer.cc 編集チーム公開日 更新日

質問

プロダクトのアクティビティフィードが100,000件まで増加する可能性があります。カーソルベースのAPIは1リクエストにつき50件を返し、画像読み込みや詳細展開によってカードの高さは48〜240ピクセルで変動します。白画面のちらつき、スクロールの跳び、下書きの消失、キーボードフォーカスの喪失、スクリーンリーダーの誤認を発生させずに、どのように仮想化しますか?

プロンプトと適用シナリオ

プロダクトのアクティビティフィードは、論理的に100,000件のアイテムまで増加する可能性があります。カーソルベースのAPIは1回のリクエストで50件を返します。カードの高さは、テキストの折り返し、画像の読み込み、詳細の展開などにより48〜240ピクセルの間で変動します。ユーザーはフィードのフィルタリング、カードの展開、インラインでの返信下書き作成が可能です。キーボードやスクリーンリーダーのユーザーは、フォーカスや位置情報を失うことなくフィード内を移動できる必要があります。

描画範囲、可変高の計測、インクリメンタルローディング、状態の所有権、スクロールアンカリング、フォーカス動作、アクセシビリティのセマンティクス、および検証計画を設計してください。よりシンプルなページネーションや非仮想化リストが適しているケースについても説明してください。

アイテム数、ページサイズ、高さの範囲は面接用の前提条件であり、普遍的な製品目標ではありません。この設問はシニアフロントエンド、UIインフラストラクチャ、Webパフォーマンスのロールに適しています。コアスキルはブラウザレンダリングとインタラクション設計であるため、カテゴリは frontend です。

面接官が評価するポイント

第一に、混同されがちな4つの関心事を候補者が分離できるかです。ページネーションはクライアントがどのレコードを読み込んだかを制御します。仮想化は読み込まれたレコードのうちどれがDOMノードを持つかを制御します。安定した識別子(Identity)はどのビジネスレコードが状態を所有するかを制御します。アクセシビリティは部分的なDOMが論理的なコレクションをどのように表現するかを制御します。1つの仕組みで残り3つを解決することはできません。

第二に、候補者が行サイズの前提から範囲計算を導出できるかです。等高の行であれば直接オフセット計算が可能です。可変高の行では、推定値、実測サイズ、累積オフセット、および推定値変更時の補正ポリシーが必要になります。優れた回答では、ビューポート上部のコンテンツ変更によって生じるスクロールの跳びを指摘し、論理アンカーを保持します。

第三に、候補者が状態の破壊を防げるかです。リサイクルされる視覚的なスロットはビジネスレコードではありません。下書き、選択状態、展開状態、保留中の変更(ミューテーション)には安定したレコードIDが必要であり、行がアンマウントされても維持される状態管理が求められます。ソート、フィルタリング、古いページレスポンスによって、誤ったインデックスにデータや状態が紐づいてはなりません。

第四に、候補者がアクセシビリティを損なわずに維持できるかです。可視カードのみをマウントすると、論理セットの大部分がDOMから削除されます。優れた回答では、ネイティブまたはフィードのセマンティクスを維持し、論理的な位置とセットサイズを公開し、busy状態を正しく通知し、フォーカスのあるカードをマウントしたまま保持し、操作可能な読み込み手段を提供します。

第五に、候補者が「スムーズなスクロール」と約束するだけでなく、動作を検証できるかです。回答では対象デバイスとシナリオを定義した上で、マウントされたノード数、フレームおよびロングタスクの挙動、レイアウトシフトやアンカー誤差、重複リクエスト、メモリ、フォーカスの保持、スクリーンリーダーの出力を測定する必要があります。

最初に確認すべき質問

  • 仮想化なしでは本当に遅いのか? プロダクトが最大でも数十件の単純な行を表示するだけなら、通常のレンダリングやページネーションの方が保守が容易でアクセシブルです。ウィンドウイングを導入する前に計測してください。
  • 行の高さは固定か、範囲限定か、任意か? 固定高であればO(1)の範囲計算が可能です。可変高の場合は推定と計測が必要です。高さ制限のない埋め込みや遅延メディアはスクロール補正を困難にするため、カードレイアウトの制約を検討する根拠になります。
  • APIは総件数を返すか、次のカーソルのみか? 総件数が既知であれば、完全な論理セットサイズをサポートできます。総件数が未知の場合、スクロールバーの期待値やアクセシビリティのメタデータが変わります。また、カーソルページネーションでは未ロードのインデックスへ直接アクセスできません。
  • ユーザーは任意のアイテムへジャンプする必要があるか? 可変高で未計測の行が多い場合、インデックスによるジャンプは近傍の行が計測されるまで概算になります。レコードIDによるジャンプには、サーバー側の検索や別のAPIが必要になる場合があります。
  • どの行状態がアンマウントを生き残る必要があるか? 一時的なホバー状態は破棄しても問題ありません。返信の下書き、選択、展開、バリデーションエラー、楽観的更新は、通常、行コンポーネントの外部でレコードIDをキーとした状態に保持すべきです。
  • どのようなキーボードモデルとセマンティクスを適用するか? 閲覧用フィード、選択可能なリストボックス、編集可能なグリッドでは、ロールとキーボードの契約が異なります。視覚的なレイアウトではなく、インタラクションからセマンティックパターンを選択してください。
  • データ変更時のスクロール契約はどうあるべきか? フィルタリング後、製品仕様としては最上部にリセットされる場合があります。古いアイテムが先頭に追加(prepend)された場合、通常は現在表示されている最初のアイテムを維持します。ライブアイテムが末尾に追加(append)された場合、ユーザーがすでに末尾にいた場合のみ追従します。
  • パフォーマンスの受け入れ基準は何か? 対象デバイス、データの形状、スクロールアクション、予算(バジェット)を定義してください。小さなプレースホルダー行でのデスクトップトレースは、画像やコントロールを含むモバイルフィードの検証にはなりません。

30秒の回答フレームワーク

「データ読み込み、仮想化、行状態を明確に分離します。カーソルローダーは50件のページを取得・重複排除します。バーチャライザーは可視範囲とわずかなオーバースキャンのみを描画し、読み込み済みデータを表すスペーサー内にそれらを配置します。固定高の行はオフセット計算を用い、可変高の行は推定値から開始してレイアウト後に計測し、累積オフセットを更新します。ビューポート上部の高さが変化した際は、画面が跳ばないよう最初の可視レコードと行内オフセットを保持します。すべての行はレコードIDを使用し、下書きや選択状態は行の外部で管理します。アクティビティフィード用にはfeedおよびarticleセマンティクス、論理位置とセットサイズ、busy状態を公開し、フォーカスを持つカードは絶対にアンマウントしません。高速スクロール、画像・展開の変更、フィルタの競合、先頭・末尾追加の挙動、キーボードおよびスクリーンリーダー操作、ノード数、ロングタスク、メモリ、アンカー誤差をテストします。通常のレンダリングで要件を満たせるなら、よりシンプルなリストを採用します。」

ステップごとの詳細解説

ステップ1:読み込まれたレコードとマウントされた行を分離する

データ層の明示的な状態を維持します:順序付きレコードID、IDからレコードへのマップ、次のカーソル、リクエストステータス、および追加データの有無。バーチャライザーは順序付けられた読み込み済みIDとスクロールコンテナを受け取ります。カーソル、リトライ、下書き、サーバーミューテーションは所有しません。

カーソルAPIでは、初期状態で50件のレコードのみがアドレス可能です。読み込み済みのプレフィックスを仮想化し、ページが届くにつれて論理的高さを拡張します。APIがインデックスアクセスをサポートしているか、安定した位置を持つプレースホルダーを返さない限り、未読み込みの73,421番目のインデックスがローカルで利用可能であるかのように装ってはなりません。可視範囲の末尾が読み込み済み件数に近づいたときにフェッチをトリガーできますが、リクエストの結合(coalescing)と終端での停止はローダーの責務です。

この分離により、何千もの古いDOMノードがマウントされたままになる無限スクロールのバグや、狭い範囲が再計算されるたびに重複リクエストを発行するバーチャライザーのバグという、2つの一般的な不具合を防ぎます。

ステップ2:可変高を追加する前に固定高の範囲を導出する

等高の行の場合、scrollTop、ビューポートの高さ、および行の高さから最初と最後の可視インデックスを導出します。オーバースキャンによって範囲は拡張されますが、読み込み済み件数でクランプされます。最大高の内部キャンバスによってスクロールバーが生成され、マウントされた行はその論理オフセットに配置されます。

js
function getFixedRange({
  scrollTop,
  viewportHeight,
  rowHeight,
  loadedCount,
  overscanRows,
}) {
  const visibleStart = Math.floor(scrollTop / rowHeight);
  const visibleEnd = Math.ceil(
    (scrollTop + viewportHeight) / rowHeight,
  );

  return {
    start: Math.max(0, visibleStart - overscanRows),
    end: Math.min(loadedCount, visibleEnd + overscanRows),
    totalHeight: loadedCount * rowHeight,
  };
}

start から end までの半開区間(end は除外)を描画します。インデックス i の行は i * rowHeight から始まります。スクロールコンテナから範囲を更新し、フレームワークで描画をバッチ処理し、1回のスクロールコールバック内でレイアウトの読み取りとスタイルの書き込みを繰り返すのを避けます。

この手法は、デザイン上で単一の行の高さ、または少数の既知のバリアントを強制できる場合に適しています。予測可能で、軽量かつ復元が容易です。ライブラリが提供しているからという理由だけで動的計測を採用してはなりません。

ステップ3:可変行を計測し、累積オフセットを維持する

可変行では、乗算をサイズモデルに置き換えます。未計測の各行は、実際のカードデザインに基づいた妥当な推定値から開始します。計測されたサイズは、リサイクルされるDOMスロットではなく、レコードIDごとに保持します。アイテムの開始オフセットは先行するサイズの合計であるため、最初の可視アイテムの検出は累積オフセットに対する探索となります。

単純なプレフィックス配列は説明が容易ですが、1つの高さが変わったときに以降の多くのオフセットを更新する必要があります。バーチャライザーライブラリやインデックス付きプレフィックスサム構造を使用すると、探索と更新のコストを削減できます。面接で重要な決定事項は不変条件です。描画されるすべての行は同一のサイズストアから導出された論理開始オフセットを持ち、合計キャンバス高は現在の計測または推定サイズの合計と等しくなります。

行のレイアウト後に ResizeObserver またはライブラリの計測フックを使用します。可能な場合は既知の寸法から画像領域を確保します。そうでない場合、画像の読み込みによって新たな計測が発生します。変化のないサイズは無視し、補正をバッチ処理し、アンマウントされた行のオブザーバーは解除します。幅、フォントメトリクス、展開、またはコンテンツの変更によって高さが変わる可能性がある場合は再計測します。

推定値がずれること自体は許容されます。ユーザーが見ている対象を維持するポリシーなしにオフセットを変更してしまうと、システムは破綻します。

ステップ4:論理スクロールアンカーを保持する

サイズ変更を適用する前に、最初の可視レコードIDと、その上端からビューポート上端までの距離を取得します。計測値と累積オフセットを更新した後、そのレコードの新しい開始位置を見つけ、行内の同一ポイントが同一の視覚的位置にとどまるようにスクロールオフセットを設定します。

アンカーより完全に上にある行が30ピクセル拡大した場合、スクロールオフセットには通常同じ30ピクセルの補正が必要です。アンカーより下の変更では補正は不要です。ユーザーがスクロールバーをドラッグしている最中や、製品が意図的に新しいアイテムへナビゲートした場合は、そのインタラクション契約に従って補正を遅延またはスキップします。

ブラウザも通常のレイアウト変更に対してスクロールアンカリングを実装しています。バーチャライザーは擬似的なキャンバスと絶対配置の行を使用することが多いため、ネイティブの挙動では意図した論理アイテムが保持されない場合があります。組み合わせをテストしてください。特定のコンテナに対しては、ブラウザかバーチャライザーのどちらか一方のみに補正を担当させます。手動アンカリングを正とする場合は、二重補正を防ぐためにそのコンテナでネイティブのアンカリングを無効化(opt-out)します。

古いレコードの先頭追加(prepend)にも同じルールを適用します。現在のアンカーを取得し、新しいレコードを挿入・計測してから、アンカーを復元します。ライブレコードの末尾追加(append)は、ユーザーがプロダクトの「最新位置」の閾値内にいた場合のみ末尾に追従します。そうでない場合はビューポートを動かさず「新しいアクティビティ」アクションを表示します。

ステップ5:行ごとの処理負荷とスクロール挙動からオーバースキャンを調整する

オーバースキャンは、ビューポートのすぐ外側にアイテムをマウントすることで描画遅延を隠蔽します。少なすぎると高速スクロール時に空白領域が露出し、多すぎるとDOM、レイアウト、メモリ、エフェクトのコストが増大し、仮想化の目的が損なわれます。可変高の場合、5つのコンパクトな行と5つの展開された行では負荷も距離も異なるため、固定行数よりもピクセルベースのオーバースキャンの方が安定することが多いです。

控えめな設定から開始し、ターゲットデバイス上でトレースを記録して、根拠に基づいて調整します。スクロールの方向、速度、行の描画コスト、画像デコード、フレームワークの更新遅延を考慮してください。オーバースキャンは、重い行処理のメモ化、メディア寸法の確保、不要になったエフェクトのキャンセル、または同期的なレイアウト読み取りの削減の代わりにはなりません。

厳密なノード数の不変条件を維持します。マウントされる行は、可視範囲、オーバースキャン、およびフォーカスされたアイテムなど明示的にピン留めされた少数の行の合計と等しくなります。メモリプロファイルでは、古い行コンポーネント、オブザーバー、イベントリスナーがそのセットから外れた後にガベージコレクション可能になることを確認する必要があります。

ステップ6:競合や重複レコードなしでページを読み込む

リサイクルされて消える可能性があるセンチネルに頼るのではなく、論理範囲の終端から読み込みをトリガーします。1つのカーソルに対して最大1回のリクエストのみを許可します。各リクエストに関連付けられたカーソルを保存し、返されたレコードを安定したIDで重複排除し、サーバーから次のカーソルがないと示されたら停止します。

フィルタリングやソートを行うと、新しいクエリ世代(generation)が作成されます。可能な場合は直前のリクエストを中止し、世代が一致しなくなった遅延レスポンスは破棄します。順序付きID、カーソル、レイアウトやコンテンツに依存する計測値、およびスクロール位置を製品の契約に従ってリセットします。新しい順序に対して古いオフセットを再利用すると、正しい高さが間違った位置に適用されてしまいます。

読み込みプレースホルダーには安定した論理的処理が必要です。1つのプレースホルダーが次のページを表す場合、それは50件のアクセシブルなレコードではありません。APIが既知のインデックス付き合計を提供し、製品が行ごとのプレースホルダーを選択する場合は、それらに安定した位置を与え、IDをシフトさせることなく置き換えます。常にリトライとリスト終端の状態を公開してください。フェッチが失敗した際に、終端に見える恒久的な空白の末尾を残してはなりません。

ステップ7:リサイクルされる行の外部にレコード識別子と重要な状態を保持する

フレームワークのキーおよびバーチャライザーのアイテムキーとして、各レコードの安定したIDを使用します。ページの先頭追加、フィルタによる並べ替え、重複削除が行われる場合、配列のインデックスを使用するのは危険です。リサイクルされたスロットは現在レコードAを表示し、後でレコードBを表示する可能性があります。スロットに紐づいた状態は、ユーザーレコード間で下書き、チェックボックス、または楽観的更新結果をリークさせる恐れがあります。

永続的なUI状態は、レコードIDをキーとするマップとして保存します:下書きテキスト、展開、選択、バリデーションステータス、保留中のミューテーションなど。行はマウント時に自身のデータを読み取り、アンマウント後も存続するオーナーを通じて書き込みます。失われても問題のない一時的な表示詳細については、ローカル状態で十分です。

すべての一時的な値を永久に保持し続けないでください。プロダクトがクエリを破棄したときに状態を削除し、下書きの保持期間を制限し、楽観的状態をサーバーの結果と調整します。目標は無制限のクライアントキャッシュではなく、ビジネスアイデンティティによる所有権の確立です。

ステップ8:アクセシビリティを範囲アルゴリズムの一部として設計する

インタラクションからセマンティクスを選択します。閲覧フィードの場合、コンテナはfeedセマンティクスを使用し、各カードはアクセシブルな名前を持つarticleセマンティクスを使用できます。マウントされた各articleは、1から始まる論理位置を公開します。合計が既知の場合は論理合計を公開し、未知の場合はパターンのサイズ未知表現を使用します。複数操作の更新中はフィードをbusy状態としてマークし、更新完了時には必ずその状態をクリアします。

DOMフォーカスを持つ要素をアンマウントしてはなりません。通常のオーバースキャンの外側に外れた場合でも、フォーカスされたレコードをマウント範囲に追加するか、破棄される前に意図的なナビゲーションアクションを通じてフォーカスを移動します。キーボードコマンドで次の論理記事に移動する際は、それが読み込まれてマウントされていることを確認し、ビューにスクロールしてからフォーカスを当てます。カスタムキーを文書化し、フィードの前後へ確実に移動できる手段を維持します。

画面外のコンテンツがDOMに存在しないため、仮想化によってページ内検索、ブラウザのテキスト選択、印刷、スクリーンリーダーの閲覧動作が損なわれる可能性もあります。これらの機能が要件であるかを判断してください。検索にはサーバー連携のコントロールが必要になる場合があり、印刷には個別のページ分割ビューを描画することがあり、明示的な「さらに読み込む」コントロールは自動読み込みに対する操作可能な代替手段となり得ます。

最後に、キーボードのみの操作および実際のスクリーンリーダーでテストします。属性の検査はマークアップの確認にはなりますが、フォーカス、アナウンス、読み込み、ナビゲーションが連携して動作することを証明できません。

ステップ9:1回のスムーズスクロールではなく、障害マトリクスを検証する

長短のテキスト、寸法付き遅延画像、展開、メディアの欠落、重複IDを含む決定論的なデータセットを構築します。低速・高速なホイールスクロール、タッチの慣性スクロール、スクロールバーのドラッグ、リサイズ、ズーム、フォント読み込み、リクエスト中のフィルタ変更、先頭追加、末尾追加、フェッチ失敗、リトライ、データ終端をテストします。

テスト実行全体を通じてマウントされた行数とDOMノード数を記録します。パフォーマンストレースを使用して、フレーム、ロングタスク、スクリプティング、レイアウト、ペイント、予期しない反復計測を検査します。多数のページをスクロールした後のピークメモリと保持メモリを追跡します。高さ変更前後の上部論理アンカーを比較し、重複カーソルやIDが表示されないことを検証します。

インタラクションについては、行内にフォーカスを置き、ポインタやキーボードでスクロールし、その上部にある行を展開し、次のページを読み込み、フィルタを変更します。フォーカスの着地場所と、下書きが正しいIDに紐づいたままであるかを確認します。スクリーンリーダーを使用して、記事名、論理位置とセットサイズ、busy状態の遷移、前後のナビゲーション、リトライ、終端状態を検証します。

受け入れ基準はプロダクトおよび対象デバイスに依存します。仮想化が特定のフレームレートを保証すると主張するのではなく、計測されたトレースと不変条件の違反を報告してください。

質の高い模範解答

「フィードを3つのレイヤーでモデル化します。データ層は順序付きID、レコード、次のカーソル、リクエスト世代、重複排除を所有します。バーチャライザーは読み込み済みIDのみを受け取り、可視範囲、計測、配置を所有します。下書き、展開、選択、ミューテーションはレコードIDをキーとするマップに保持し、行がアンマウントされても削除や移動が発生しないようにします。

固定行の場合、範囲は直接オフセット計算となり、キャンバスの高さは読み込み件数 × 行高となります。今回のカードは48〜240ピクセルで変動するため、推定値から開始してマウントされたカードを計測し、累積オフセットを維持します。最初の可視レコードと行内オフセットがスクロールアンカーを形成します。そのレコードより上の計測値が変化した場合、その差分だけスクロールオフセットを更新します。ネイティブと手動のスクロールアンカリングが二重に調整しないよう、コンテナに対して1つの補正オーナーを選択します。

範囲には計測済みの小さなオーバースキャンとフォーカスされたカードが含まれます。終端が読み込み済み件数に近づくと、ローダーは次のカーソルを1回だけ要求します。フィルタ変更時は新しい世代を開始し、可能な限り古いリクエストをキャンセルし、遅延結果を破棄して、順序と計測値をリセットします。レコードIDをアイテムキーとして使用し、インデックスに状態を持たせることはありません。

アクセシビリティの観点から、このアクティビティストリームにはfeedとarticleのセマンティクスを使用します。マウントされた記事はその論理位置と既知の合計を公開し、フィードはページ挿入中にbusy状態を公開して完了後にクリアします。フォーカスを持つ記事はアンマウントしません。キーボードナビゲーションは次の論理記事を読み込んでマウントしてからフォーカスを当て、リトライ、終端、手動読み込みの明確なパスを保持します。

まず、通常のレンダリングで目標予算を達成できないことを確認します。その後、行の高さ、遅延メディア、展開、高速スクロール、リサイズ、フィルタの競合、先頭・末尾追加、ネットワーク障害の決定論的な組み合わせをテストします。マウントされたノード数、ロングタスク、レイアウト・ペイント処理、保持メモリ、アンカー誤差、重複リクエスト・ID、下書きの同一性、キーボードフォーカス、スクリーンリーダーの出力を記録します。ページネーション付きの通常リストで同一要件を満たせるなら、正確性とアクセシビリティの障害モードが少ないそちらを選択します。」

よくある間違い

  • 無限スクロールを仮想化と混同する → 古いページがDOMノードを追加し続け、スクロール距離に応じてパフォーマンスが低下する → 読み込まれたデータとは独立してマウント範囲を制限する。
  • 配列のインデックスをアイテムキーとして使用する → 先頭追加、フィルタリング、重複排除によって下書きや選択が異なるレコードに移動する → 行および外部状態のキーには安定したレコードIDを使用する。
  • 1つの平均高が正確であると思い込む → オフセット誤差が累積し、空白範囲や跳びが発生する → 可変行を計測し、単一の累積サイズモデルを更新する。
  • アンカーなしで計測変更を適用する → ビューポートより上のコンテンツによって、ユーザーが読んでいる位置がずれる → 最初の可視レコードと行内オフセットを保持する。
  • ネイティブと手動の両方のアンカリングで補正させてしまう → ビューポートが2回移動する可能性がある → コンテナをテストし、補正を1つのオーナーに割り当てる。
  • 固定の大きな行数でオーバースキャンする → 展開されたカードによって重いDOMおよびレイアウト処理が再発する → ターゲットデバイスでピクセル距離と描画コストを調整する。
  • すべての範囲更新からフェッチを発行する → レンダリングの繰り返しによって同一カーソルが要求され、結果が交差する → カーソルごとにリクエストを結合し、古いクエリ世代を破棄する。
  • 行コンポーネントの内部に下書きを保持する → アンマウントによって削除されるか、スロットの再利用によって他のデータへリークする → 重要な状態はIDをキーとするオーナーに引き上げる。
  • フォーカスされた行をアンマウントする → キーボードおよび支援技術のコンテキストが消失する → フォーカスされたアイテムをピン留めするか、破棄前に意図的にフォーカスを移動する。
  • マウントされたサブセットの位置のみを通知する → スクリーンリーダーが誤ったコレクションサイズと順序を認識する → 論理位置および既知または未知の論理セットサイズを公開する。
  • busy状態を設定したままクリアしない → 支援技術が完了した更新を認識できない可能性がある → 成功および失敗のすべてのパスでbusy状態をクリアする。
  • フレームレートのみを最適化する → フォーカス、下書き、競合、メモリ、スクロール位置が依然として不正である可能性がある → パフォーマンスとインタラクションの不変条件を合わせて検証する。

フォローアップの質問と回答

フォローアップ1:すべての行が正確に56ピクセルの高さである場合、何が変わるか?

動的計測とその補正パスを削除します。56ピクセルから直接範囲とオフセットを計算し、乗算によって読み込み済み総高さを導出し、インデックスと行内オフセットによって位置を復元します。安定したID、読み込み競合、フォーカス、アクセシビリティの処理は維持します。固定高はジオメトリを単純化しますが、同一性やインタラクションの問題を解決するわけではありません。制約上不要になったプレフィックスサム構造を追加する前にベンチマークを行ってください。

フォローアップ2:カーソル専用APIから未知の総件数をどのようにサポートするか?

読み込み済みのプレフィックスを仮想化し、ページが届いたときにキャンバスを拡張します。未読み込みのインデックスや偽の最終スクロールバーサイズを作成しないでください。フィードパターンの未知のセットサイズを公開し、読み込み中および終端状態をアナウンスし、明示的な追加読み込みやリトライのコントロールを維持します。サーバーがインデックス付きルックアップ、レコードロケーター、または安定した推定契約を追加しない限り、任意のインデックスへのジャンプは利用できません。

フォローアップ3:ユーザーが中間を読んでいる間に古いアイテムが先頭追加された場合、どのように跳びを抑えるか?

挿入前に、最初の可視レコードIDとビューポートに対する相対オフセットを取得します。古いIDを挿入し、それらを推定または計測し、累積オフセットを再計算して、取得したレコードが同一の視覚的位置に戻るようにスクロールオフセットを設定します。ページの境界でIDを重複排除します。メディア読み込み後に計測が続く場合は、同じルールを使用してアンカーより上の差分を適用します。

フォローアップ4:スクロールバーのドラッグ後、フォーカスされたカードが5,000アイテム離れた場合、永久にマウントしたままにするか?

インタラクション契約を定義します。1つのフォーカスされたカードをピン留めし続けることは限定的なDOMコストで済みますが、ビューポートから遠く離れた隠れたフォーカスターゲットはユーザーを混乱させる可能性があります。意図的なスクロールバーのジャンプ中は、アナウンスとともにフィードコンテナまたは新しくナビゲートされた可視記事にフォーカスを移動し、古いカードのピン留めを解除します。突発的な計測やポインタによるスクロール中は、フォーカスを勝手に破棄してはなりません。選択した動作をキーボードおよびスクリーンリーダーのユーザーでテストしてください。

フォローアップ5:プロダクト側からブラウザのページ内検索と全10万件の印刷が求められた場合、1つの仮想化DOMで満たせるか?

信頼性のある部分DOMでは、マウントされていないテキストをブラウザ検索に公開したり、通常のページコンテンツとして印刷したりすることはできません。これらは個別の要件として扱います。一致するレコードへ移動してマウントするサーバー連携検索を提供します。100,000枚のインタラクティブカードを一時的にマウントしてページをフリーズさせるのではなく、明示的な制限を設けたページ分割エクスポートまたは印刷専用のデータビューを生成します。

フォローアップ6:スクリーンショットだけに頼らず、どのようにバーチャライザーをテストするか?

決定論的な入力を用いて純粋な固定範囲および累積オフセットの不変条件をテストし、その後、実際のブラウザで統合シナリオを実行します。マウントされたIDが可視範囲とオーバースキャンをカバーしていること、合計サイズがサイズモデルと一致していること、計測後に論理アンカーが許容範囲内にとどまっていること、各カーソルが1回だけ要求されていること、並べ替え時に下書きがIDに追従することをアサートします。DOMジオメトリのアサーションではフォーカスやアナウンスを検証できないため、キーボードおよびスクリーンリーダーのチェックを追加します。

公開情報ソース

関連する質問