【実務・中級編】 Intersection Observerによる遅延レンダリング – Webブラウザの仕組み実践ガイド

やあ。今日も今日とて、重い初期レンダリングのパフォーマンスチューニングに頭を悩ませているかい?

「プロダクトの機能が増えるにつれて、初回ロード時のDOMツリーが数千ノードに膨れ上がり、メインスレッドが悲鳴を上げている……」
「モーダルや無限スクロール、大量の画像リストのせいで、LCP(Largest Contentful Paint)やTBT(Total Blocking Time)のスコアがみるみる悪化していく……」

フロントエンドの現場で生きている私たちにとって、こうしたパフォーマンスの壁は避けて通れない登竜門だ。そして、この壁をスマートに、かつ美しく突破するための現代の特効薬が、`Intersection Observer API`を用いた遅延レンダリングだ。

今回は、この強力なAPIがブラウザの内部でどう動いているのかという仕様の深部から、現場で即座に使える実践的なコードまで、シニアの視点でみっちり解説していこう。

—

なぜ従来の「`scroll`イベント+`getBoundingClientRect`」は悪手なのか?

まず、私たちが昔からやってきた「失敗」の話をしよう。要素がビューポートに入ったかどうかを検知するために、`window.addEventListener(‘scroll’, …)`を仕込み、その中で各要素の`getBoundingClientRect()`をぶん回す……。

あれは、ブラウザの仕組みを知る者からすれば「メインスレッドへの拷問」に等しい。

ユーザーがスクロールするたびに`scroll`イベントは凄まじい頻度で発火する。その都度JavaScriptが走り、レイアウト(リフロー)を引き起こす`getBoundingClientRect()`を呼び出す。するとブラウザは、画面の描画を一旦停止して位置計算を強制される。これが、あのカクついたスクロール(Jank)の正体だ。

ブラウザを救う救世主:`Intersection Observer`の内部動作

では、`Intersection Observer`は何が違うのか?
最大の違いは、「メインスレッドをブロックせず、ブラウザの内部処理として非同期で交差判定を行ってくれる」という点だ。

ブラウザのレンダリングエンジン(BlinkやWebKitなど)は、内部でレイアウトやペイントのライフサイクルを持っている。`Intersection Observer`を使うと、開発者はブラウザに対して「この要素とビューポートが交差したら、私に教えてくれ」とタスクを登録できる。

ブラウザは、自身の最適化されたタイミング(通常は描画の直前など)で静かに交差状態をチェックし、交差が起きたときだけメインスレッドにコールバックを投げてくれる。つまり、私たちがJSで血眼になって位置を計算し続ける必要が一切なくなるのだ。これこそが、モダンブラウザのアーキテクチャの恩恵を受けるということだよ。

—

実践:Intersection Observerによる遅延レンダリングの全体像

百聞は一見に如かずだ。今回は、実務でよくある「画面内に入るまで重いコンポーネント(あるいは画像やプレースホルダー)のDOMを生成・描画しない」というパターンの、洗練された実装例を見ていこう。

そのままエディタにコピペして、ローカルの静的サーバー等で動かせるようにプレーンなJavaScript(TypeScriptでも考え方は全く同じだ)で記述している。





Intersection Observer 遅延レンダリング実践デモ


↓ 下にスクロールして遅延レンダリングを体験してください ↓

コンテンツを読み込んでいます…

さらに下にスクロール

コンテンツを読み込んでいます…



—

現場で役立つシニアからのTips&ベストプラクティス

このコードだけでも十分に動くが、実務の現場に放り込むとなると、いくつかの「罠」や「こだわり」を知っておく必要がある。

1. `rootMargin` で「先読み」のUXを仕込む

コード内でも設定しているが、`rootMargin: ‘200px 0px’` のようにマージンを持たせるのがプロの技だ。
ユーザーがスクロールして「画面に入った瞬間」にロードを始めると、ネットワークの遅延やJSのパース時間によって、数瞬だけ「ローディング表示(チラツキ)」が見えてしまう。
あらかじめ上下にバッファ(例: 200px手前)を持たせておくことで、ユーザーが視認する前に裏側でこっそりレンダリングを完了させ、「最初からそこに存在していたかのような滑らかなUX」を提供できる。

2. 使い終わったら必ず `observer.unobserve()` を呼ぶ

監視対象の要素が一度ビューポートに入り、コンテンツの描画が終わったのであれば、速やかに監視を解除(unobserve)すること。
これをサボると、ユーザーが再びその要素の周辺までスクロールバックしたときに不要なコールバックが走り続け、メモリリークや思わぬバグの温床になる。

3. リサイズや動的DOM追加への配慮

もしシングルページアプリケーション(SPA)等で、非同期にDOMが後から追加されるアーキテクチャ(ReactやVueなど)を採用している場合は、新しいDOMがマウントされたタイミングで再度`observer.observe()`に突っ込んでやる必要がある。
Reactであれば、カスタムフック(`useIntersectionObserver`など)に抽象化してライフサイクルと綺麗に同期させると、コードベースが汚染されずに済む。

—

まとめ:ブラウザの仕組みを味方につけよう

フロントエンドのパフォーマンスチューニングの本質は、「ブラウザに無駄な仕事をさせないこと」に尽きる。

画面の最初から最後まで、すべてのDOMツリーを構築し、スタイルを計算し、画像を読み込ませるなんていうのは、ブラウザに対する暴力的ないじめだ。ユーザーのデバイススペックは千差万別であり、ハイエンドなスマホばかりではないことを忘れてはいけない。

`Intersection Observer`を正しく使いこなし、「今、本当に必要なものだけを、必要なタイミングで描画する」。このアーキテクチャ思想をチームに浸透させることができれば、君のプロダクトのパフォーマンスは見違えるほど軽快になるはずだ。

さあ、今日のコードをさっそく次のプルリクエストに活かしてくれ。健闘を祈る!

コメント

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