IntersectionObserver を使うと、要素が画面(ビューポート)に入った・出たタイミングを効率的に監視できます。スクロールに合わせた表示切り替えやアニメーション、遅延読み込みなどに使われます。
この記事では、基本の使い方から複数要素の監視、オプションの調整、一度きりの発火、そして scroll イベントより優れている理由まで解説します。
new IntersectionObserver(コールバック, オプション) を作り、監視したい要素に observe() を呼びます。コールバックでは entry.isIntersecting で「見えているか」を判定。発火位置は threshold / rootMargin で調整し、一度きりにするなら unobserve() を使います。完成イメージ(動くデモ)
↓ このボックスをゆっくりスクロールすると、画面に入ったものだけ色が変わります:
下にスクロール ↓
このデモは、スクロール領域(このボックス自身)を root に指定しています。詳しくは後述します。
IntersectionObserverの基本(コールバックとオプション)
監視対象が条件を満たすと、コールバックが呼ばれます。entries には変化した要素の情報が入り、entry.isIntersecting で画面内に入っているかが分かります。
const callback = (entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("visible"); // 画面に入った
} else {
entry.target.classList.remove("visible"); // 画面から出た
}
});
};
const options = {
root: null, // null=ビューポートを基準にする
rootMargin: "0px", // 基準の余白(早め/遅めの発火に使う)
threshold: 0.5, // 要素が50%見えたら発火
};
const observer = new IntersectionObserver(callback, options);
複数要素を監視する(querySelectorAll + observe)
複数の要素を監視するのは簡単で、監視したい要素それぞれに observe() を呼ぶだけです。1つのオブザーバで何個でも監視できます。
// .item を持つすべての要素を監視
const elements = document.querySelectorAll(".item");
elements.forEach((element) => {
observer.observe(element);
});
<style>
.item { height: 200px; margin: 50px; background: lightblue; }
.item.visible { background: lightcoral; } /* 画面内のとき */
</style>
<div class="item">Item 1</div>
<div class="item">Item 2</div>
<div class="item">Item 3</div>
オプションを使いこなす(threshold / rootMargin)
発火のタイミングはオプションで細かく調整できます。
threshold:どれくらい見えたら発火するか。0=少しでも見えたら、1=完全に見えたら。[0, 0.5, 1]のように配列で複数も指定できるrootMargin:基準範囲の余白。"200px"にすると画面に入る200px手前で発火(先読みに便利)root:監視の基準。nullでビューポート、特定要素を指定すればそのスクロール領域が基準になる
const observer = new IntersectionObserver(callback, {
rootMargin: "200px", // 200px手前で発火(画像の先読みなどに)
threshold: 0,
});
スクロール領域を基準にする(root指定)
root に要素を指定すると、ビューポートではなくその要素のスクロール領域を基準に交差を監視します。上のデモのように、内部スクロールするボックス内での出現判定に使えます。
const scrollBox = document.getElementById("scroll-box");
const observer = new IntersectionObserver(callback, {
root: scrollBox, // このボックスのスクロールを基準にする
threshold: 0.6,
});
scrollBox.querySelectorAll(".io-item").forEach((el) => observer.observe(el));
一度だけ発火させる(unobserve)
「最初に表示されたとき一度だけ」アニメーションさせたい場合は、発火後に unobserve() で監視を解除します。
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("visible");
obs.unobserve(entry.target); // この要素の監視を停止
}
});
});
// すべての監視を止めるなら observer.disconnect();
unobserve() を呼び忘れると、スクロールで要素が出入りするたびに何度も発火し、アニメーションが繰り返されたり処理が無駄に走ったりします。「初回だけ」で良い処理は、発火直後に unobserve() する習慣をつけましょう。なぜscrollイベントより良いのか
同じことは scroll イベント+getBoundingClientRect() でもできますが、IntersectionObserver の方が高速です。
scrollは1回のスクロールで何十回も発火し、そのたびに全要素の位置を計算するとカクつくIntersectionObserverはブラウザが交差を効率的に監視し、変化したときだけコールバックを呼ぶ(スクロールイベントを張る必要がない)
要素が画面内にあるかをその場で判定したいだけなら、要素が画面内にあるかどうかを判別する方法もあわせて参考にしてください。
活用例(フェードイン・遅延読み込み)
クラスを付けるだけで、さまざまな演出に応用できます。具体的な実装は次の記事で解説しています。
- スクロールでふわっと表示 → IntersectionObserverでふわっと表示させる方法
- 横からスライドイン → 画面の横からフェードインさせる方法
- 画像の遅延読み込み(Lazyload) → Lazyload(遅延読み込み)完全ガイド
よくある質問(FAQ)
scroll イベントよりパフォーマンスが高く、Lazy Load・アニメーション・無限スクロールなどに活用されます。new IntersectionObserver(callback, { threshold: 0.5 }) で要素の50%が見えたときに発火します。0 が少しでも見えたとき、1 が完全に見えたときです。[0, 0.5, 1] のように配列で複数の閾値も設定できます。root オプションにその要素を指定します({ root: スクロール要素 })。指定しない(null)場合はビューポートが基準になります。observer.unobserve(entry.target) を呼ぶと、その要素の監視を停止します。すべての監視を止めるには observer.disconnect() を使います。一度きりの演出では呼び忘れると繰り返し発火するので注意します。まとめ
IntersectionObserver は、new IntersectionObserver(コールバック, オプション) を作り、監視したい要素に observe() を呼ぶだけで使えます。複数要素も forEach で observe() するだけです。
発火位置は threshold(見えた割合)と rootMargin(余白)で調整し、スクロール領域を基準にするなら root、一度きりにするなら unobserve()。scroll イベントより高速なので、フェードインや遅延読み込みなどスクロール連動の処理に最適です。

