効率的なバックグラウンド処理

ウェブ機能: content-visibility

コンポーネントがブラウザでレンダリングされていないときは、システム リソースとバッテリーの消耗を抑えるために、負荷の高いバックグラウンド タスクを一時停止します。

概要

content-visibility: auto プロパティを使用すると、ビューポートから遠く離れた要素のレンダリング計算をブラウザでスキップできます。ブラウザが要素のレンダリングをスキップまたは再開することを決定すると、その要素で contentvisibilityautostatechange イベントが発生します。

このイベントをリッスンすることで、<canvas> アニメーション、WebGL レンダリング、高頻度の WebSocket データ ポーリングなどの負荷の高いオペレーションを、不要なときに一時停止し、ブラウザがコンテンツを表示する準備ができたときにジャストインタイムで再開できます。

「contentvisibilityautostatechange」対「IntersectionObserver」

どの API をいつ使用するかを理解することが重要です。

  • ビューポート内の要素の正確な視覚的視認性に関連付けられたアプリケーション ロジック(遅延読み込みデータ、無限スクロール トリガーなど)には、IntersectionObserver を使用します。
  • レンダリング負荷の高い作業(複雑なキャンバスの更新や負荷の高い DOM ミューテーションなど)には contentvisibilityautostatechange を使用します。このイベントは、ブラウザの内部レンダリング ライフサイクルに直接関連しています。ブラウザは、要素が実際に画面に表示される前にレンダリングを開始することがよくあります(プリレンダリング マージン)。このイベントは、そのタイミングを通知し、コンテンツが視聴可能な状態であることを確認します。

実装

1. CSS の content-visibility を適用する

重いコンテナに content-visibility: auto を設定し、プレースホルダのサイズを指定して、スクロールバーがジャンプしないようにします。

.heavy-component {
  /* Defer rendering work when off-screen */
  content-visibility: auto;
  
  /* Mandatory: Provide a placeholder size to prevent layouts shifts.
    - 'auto' is optional and enables the browser to remember the actual size
      once rendered. It must be paired with a <length> value to be used for
      the first render.
    - 'none' tells the browser not to apply any intrinsic width to this element.
      It can be used for either the height or the width value.
    - '500px' is the estimated height of this element. This can be any valid
      CSS <length> value. Replace it with the expected height of your
      component.
   */
  contain-intrinsic-size: auto none auto 500px;
}

2. 状態の変化をリッスンする

contentvisibilityautostatechange のイベント リスナーを追加して、バックグラウンド タスクを一時停止または再開します。

const component = document.querySelector('.heavy-component');

// Option 1: Direct listener (recommended)
component.addEventListener('contentvisibilityautostatechange', (event) => {
  if (event.skipped) {
    // The browser skipped rendering this content.
    // DO NOT perform heavy mutations or animation loops here.
    stopSimulation();
    pauseWebSocketPolling();
  } else {
    // The browser is about to render the content.
    // Resume your work so it is ready when visible.
    startSimulation();
    resumeWebSocketPolling();
  }
});

// Option 2: Capturing listener for event delegation
document.addEventListener('contentvisibilityautostatechange', (event) => {
  if (event.target.matches('.heavy-component')) {
    if (event.skipped) {
      stopSimulation();
    } else {
      startSimulation();
    }
  }
}, { capture: true });

代替戦略

content-visibility プロパティと関連する contentvisibilityautostatechange イベントは、プログレッシブ エンハンスメントです。サポートしていないブラウザの場合:

  • CSS プロパティは無視され、コンテンツは通常どおりレンダリングされます。
  • イベントは発生しないため、バックグラウンド タスクは最適化なしで通常どおり実行され続けます。

古いブラウザでタスクの一時停止をサポートする必要がある場合は、大まかな近似値として IntersectionObserver を使用するようにフォールバックできます。これにより、古いデバイスでもバッテリーと CPU の消費を抑えることができます。

// Fallback using IntersectionObserver for older browsers
const target = document.getElementById('target-container');

// Check if content-visibility is supported
const isSupported = 'contentVisibility' in document.documentElement.style;

if (!isSupported) {
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        // The element is close to the screen. Start work!
        startSimulation();
      } else {
        // The element is far away. Pause work!
        stopSimulation();
      }
    });
  }, {
    // Use rootMargin to start rendering before it hits the screen
    rootMargin: '200px'
  });

  observer.observe(target);
}