← PC・IT用語集へ戻る

Intersection Observer

Intersection Observer API
web programming beginner
要素が画面内に入ったかどうかを、ブラウザ側で効率よく監視するための仕組みのこと。
Intersection Observer (Intersection Observer API)

概要(サマリー)

Intersection Observer とは、要素が画面内に入ったかどうかを、ブラウザ側で効率よく監視するための仕組みのことである。「intersection」は交差という意味で、監視したい要素と表示領域が重なったかを見張ってくれる。

見張り役を雇うイメージだ。自分で1秒に何十回も「もう見えた? まだ?」と確認しに行く代わりに、「見えたら教えて」とブラウザに頼んでおき、実際に見えたときだけ連絡をもらう。スクロールに合わせて要素がふわっと現れる演出や、画像の遅延読み込みの土台になっている。

以下は、下にスクロールすると枠内のカードが順に現れるデモである。

↓ この枠の中をスクロールする

1枚目
2枚目
3枚目
4枚目

詳細解説

なぜこの仕組みが必要になったのか

以前は「要素が画面に入ったか」を調べるのに、スクロールのイベントを使うしかなかった。

// 昔ながらのやり方。スクロールのたびに何十回も実行される
window.addEventListener('scroll', function () {
  var rect = target.getBoundingClientRect();  // 位置とサイズを計算する
  if (rect.top < window.innerHeight) {
    target.classList.add('shown');
  }
});

この書き方には2つの問題がある。1つは、スクロール中に処理が高頻度で走ること。もう1つは、getBoundingClientRect() がブラウザに再計算を強いる重い処理であることだ。監視したい要素が10個あればそのぶん重くなり、スクロールがカクつくボトルネックになりやすい。

Intersection Observer は、この監視をブラウザ内部に任せる。画面外で待機している間はこちらの処理が一切走らないため、要素がいくつあっても軽い。

基本的な使い方

「見張り役」を作り、監視対象を登録する、という2段階で使う。

// 1. 見えたときに何をするかを決めて、見張り役を作る
var watcher = new IntersectionObserver(function (entries) {
  entries.forEach(function (entry) {
    if (entry.isIntersecting) {          // 画面内に入った
      entry.target.classList.add('shown');
      watcher.unobserve(entry.target);   // 一度出たら監視をやめる
    }
  });
});

// 2. 監視したい要素を登録する
document.querySelectorAll('.fade-item').forEach(function (el) {
  watcher.observe(el);
});

entries には、状態が変わった要素の情報がまとめて渡ってくる。entry.isIntersectingtrue なら画面内に入った、false なら出た、という判定になる。entry.target が実際のDOM要素である。

一度表示したら二度と戻さない演出なら、unobserve() で監視を解除しておくとよい。放置すると、その要素を通るたびに処理が呼ばれ続ける。

3つの設定項目

見張り役を作るときに、第2引数で細かい条件を指定できる。

var watcher = new IntersectionObserver(callback, {
  root: null,             // 基準にする領域。null は画面全体
  rootMargin: '0px 0px -100px 0px',  // 基準領域を広げる・狭める
  threshold: 0.5          // どれだけ重なったら通知するか(0.5 = 半分)
});
  • root: 何を基準に「見えた」と判定するか。既定では画面全体だが、スクロールする枠を指定すればその枠の中での判定になる
  • rootMargin: 判定ラインをずらす。下方向に -100px とすれば「画面下端より100px内側に入ったら」という判定になり、演出の発火を少し遅らせられる。逆に正の値にすれば、画面に入る手前で先読みできる
  • threshold: 何割重なったら通知するか。0 は1ピクセルでも重なった瞬間、1 は全体が入りきったとき。配列で複数指定すれば、段階的に通知を受け取れる

演出のタイミングが「早すぎる」「遅すぎる」と感じたときに調整するのは、たいてい rootMarginthreshold である。

主な使いどころ

  • スクロール演出: 画面に入った要素をふわっと表示する。CSSアニメーションと組み合わせる
  • 画像の遅延読み込み: 画面に近づいた画像だけを読み込む。初期表示を軽くできる
  • 無限スクロール: 一覧の末尾に置いた目印が見えたら、次のページを読み込む
  • 読了率の計測: 記事の末尾が見えたかどうかで、最後まで読まれたかを測る
  • 追従メニューの連動: いま画面にある見出しに応じて、目次の現在位置を光らせる

なお、画像の遅延読み込みについては、いまは <img loading="lazy"> という属性だけで実現できる。自前で組む前に、標準の属性で足りないかを確認するとよい。

注意したい点

  • 判定は非同期である。要素が見えた瞬間ぴったりに通知されるとは限らず、わずかに遅れる。1ピクセル単位で厳密な同期が要る処理には向かない
  • 監視対象が表示されていないと判定されないdisplay: none の要素は面積がゼロなので、いつまでも通知が来ない
  • 解除を忘れない。ページ内容を差し替える作りでは、不要になった見張り役を disconnect() で止めないと、監視が残り続ける
  • 動きを減らす設定への配慮。スクロール演出は、動きに敏感な人にとって負担になることがある。prefers-reduced-motion の指定がある場合はアニメーションを控えたい

NOVEBLOには、Intersection Observer を使ったスクロールアニメーションの実装手順をまとめたページがある。

AIコーディングとの関係

Intersection Observer は仕様が明快なので、AIは正確なコードを出しやすい。ただし、既定の設定のまま返ってくることが多く、演出のタイミングは自分で詰める必要がある。指示に含めたいのは次の点である。

  • 一度だけ発火させるか、出入りのたびに繰り返すか
  • どのタイミングで発火させたいか(画面に入った瞬間か、半分見えてからか)
  • 基準にする領域(画面全体か、特定のスクロール枠か)
  • 動きを減らす設定への対応が必要か

たとえば「カードが画面下端より100px内側に入り、かつ40%以上見えたらフェードインさせて。一度表示したら監視を解除して。prefers-reduced-motion のときはアニメーションなしで即表示にして」と伝えると、調整の手間がかなり減る。

生成後に確認したいのは、unobservedisconnect による後始末である。ここが抜けたコードは動作としては正しく見えるが、要素が増えるページでは監視が積み上がっていく。また、スクロール枠を root に指定する場合、その枠が overflow でスクロール可能になっていないと判定が働かない点にも注意したい。

よくある勘違い

スクロールイベントより高機能なもの?

高機能というより、目的が違う。スクロール量そのものを使いたい(進捗バーを動かす、視差効果を付けるなど)場合は、いまでもスクロールイベントが必要である。Intersection Observer が置き換えるのは「見えたかどうか」の判定だけである。

画面に入った瞬間ぴったりに呼ばれる?

通知は非同期で、わずかに遅れて届く。ほとんどの演出では問題にならないが、「スクロール位置に完全に同期して動かす」用途には向かない。

一度書けば繰り返し使い回せる?

見張り役は使い回せるが、監視対象は個別に登録が必要である。あとから追加した要素は、追加のたびに observe() を呼ばないと監視されない。一覧を継ぎ足していく無限スクロールでは、ここを忘れて「2ページ目以降が動かない」となりやすい。

対応していないブラウザが心配?

いまの主要ブラウザは対応済みである。それでも念のため備えるなら、if (!window.IntersectionObserver) で判定し、使えない場合は演出なしで最初から表示する、という書き方にしておけば内容が読めなくなることはない。

まとめ

  • Intersection Observer は、要素が表示領域と重なったかをブラウザ側で監視する仕組みである
  • スクロールイベントで毎回位置を計算する方式より軽く、要素が増えても負荷が上がりにくい
  • root / rootMargin / threshold の3つで、発火の基準とタイミングを調整する
  • 一度きりの演出なら unobserve()、不要になったら disconnect() で後始末する
  • 「見えたか」の判定に特化した仕組みであり、スクロール量そのものが要る処理には別の手段を使う

情報ソース

より詳しくAIに聞いてみよう

  • Intersection Observer とスクロールイベントの違いを、性能の観点から説明してください。
  • rootMargin と threshold の使い分けを、演出のタイミング調整の具体例つきで教えてください。
  • Intersection Observer を使って無限スクロールを実装するときの流れと注意点を教えてください。
  • 監視の解除(unobserve / disconnect)を忘れると何が起きるのか、具体的に教えてください。
  • AIにスクロールアニメーションのコードを生成してもらうとき、動きを減らす設定にも対応させるにはどう指示すればよいですか。