状態キャッシュによる SPA ビュー遷移の高速化

ウェブ機能: content-visibility

シングルページ アプリケーション(SPA)で、非アクティブなビューのレンダリング状態を破棄するのではなくキャッシュに保存することで、ビュー間の即時ナビゲーションを可能にします。

概要

従来、ユーザーが SPA のタブやビュー間を移動する際、デベロッパーは古いビューを破棄するか、display: none を使用して非表示にしていました。どちらの方法でも、ユーザーがそのビューに戻ったときに、ブラウザはレイアウト全体を再作成または再計算してペイントする必要があります。

非アクティブなビューで content-visibility: hidden を使用すると、ブラウザは要素のコンテンツをレイアウト フローから削除し、描画を停止しますが、キャッシュに保存されたレンダリング状態はメモリに保持されます。ユーザーが元のビューに戻ると、ビューはほぼ瞬時に復元されます。

CPU と RAM のトレードオフ

このアプローチはパフォーマンスを大幅に向上させますが、慎重に管理する必要がある特定のトレードオフが生じます。

  • CPU の節約: 大幅な節約。ブラウザは、非表示のビューのレイアウトとペイントパスを完全にスキップします。
  • RAM コスト: 高。ブラウザは、非表示のビューのすべての DOM ノード、イベント リスナー、状態をメモリに保持します。

このトレードオフが危険になる場合

  • 推奨事項: この戦略は、ビューの数が少なく予測可能なシンプルなアプリ(3 ~ 5 個のタブ付きインターフェースなど)に使用します。
  • 数十の固有のレポートを生成したり、無限の動的ルートを使用したりする、非常に動的なアプリケーションでは、すべてのビューを無条件にキャッシュしないでください。そうすると、最終的にメモリが大幅に肥大化し、ローエンド デバイスでブラウザがクラッシュする可能性があります。
  • 必須: アプリケーションが非常に動的な場合は、メモリしきい値に達したときに古いビューを破棄する強制排除戦略(Least Recently Used キャッシュなど)を実装する必要があります。

実装

1. ビューコンテナを構成する

現在アクティブでないビューに content-visibility: hidden を設定します。

.spa-view.inactive {
  /* MANDATORY: Use hidden to cache the rendering state of inactive views */
  content-visibility: hidden;
  
  /* Optional: Prevent hidden views from taking up physical space in layout flow */
  position: absolute;
}

2. フォーカスを管理する

ビューを入れ替える際は、ユーザー補助機能を維持するために、キーボード フォーカスを正しく管理してください。

function switchToView(viewId) {
  // Hide all views
  document.querySelectorAll('.spa-view').forEach(view => {
    view.classList.add('inactive');
    view.setAttribute('aria-hidden', 'true');
  });
  
  // Show the target view
  const activeView = document.getElementById(viewId);
  activeView.classList.remove('inactive');
  activeView.setAttribute('aria-hidden', 'false');
  
  // MANDATORY: Move focus to the new view to ensure a logical tab-order
  activeView.focus();
}

代替戦略

content-visibility プロパティは、グレースフル デグラデーションを行います。サポートしていないブラウザの場合:

  • このプロパティは無視されます。
  • フォールバック ブラウザですべてのビューが同時にレンダリングされないようにするには、CSS で display: none へのフォールバックを指定する必要があります。
@supports not (content-visibility: hidden) {
  .spa-view.inactive {
    display: none;
  }
}