Intersection Observer
要素がビューポートに入ったことを非同期で検知する Web API
Intersection Observer とは
Intersection Observer は、要素がビューポート (または指定したコンテナ) に入った/出たことを非同期で検知する Web API である。scroll イベントで自前に判定する方式と違い、交差の計算はブラウザ側でまとめて行われるため、スクロールのたびに getBoundingClientRect() を呼んでレイアウトを再計算させる必要がない。
ただし軽くなるのは判定部分だけで、コールバック自体はメインスレッドで実行される。ここに重い DOM 操作を書けば描画は普通に詰まるので、時間のかかる処理は requestIdleCallback() へ逃がす。
scroll イベントとの比較
scroll イベントとの主な違いを以下に比較する。
| 観点 | scroll イベント | Intersection Observer |
|---|---|---|
| 交差の判定 | スクロールごとに自前で計算 (レイアウト再計算を招きやすい) | ブラウザ側でまとめて計算 |
| コールバックの実行 | メインスレッド | メインスレッド (軽くなるのは判定側だけ) |
| 発火の頻度 | スクロール量に比例 (自前でスロットリングが必要) | 交差率が threshold を跨いだときだけ |
| 判定の指定方法 | ピクセル単位で自前計算 | 交差率 (threshold) と余白 (rootMargin) で宣言 |
基本的な使い方
基本的な使い方のコード例を示す。
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
observer.unobserve(entry.target); // 一度だけ検知
}
});
}, { threshold: 0.1 }); // 交差率が 10% を跨いだ時点で発火 (画面外へ出るときも発火する)
document.querySelectorAll('.lazy').forEach(el => observer.observe(el));
observe() を呼んだ直後、対象が画面外にあってもコールバックは一度実行される (初期状態の通知)。「呼ばれた = 表示された」と解釈して entry.isIntersecting の判定を省くと、ページ読み込み直後に全要素が表示済みとして処理されてしまう。遅延読み込みの意味が消えるので、分岐は必ず入れる。
画像の遅延読み込み
画像の遅延読み込みのコード例を示す。
const imgObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target as HTMLImageElement;
img.src = img.dataset.src!;
imgObserver.unobserve(img);
}
});
}, { rootMargin: '200px' }); // 200px 手前で読み込み開始
document.querySelectorAll('img[data-src]').forEach(img => imgObserver.observe(img));
rootMargin: '200px' で、ビューポートの 200px 手前から読み込みを開始し、スクロール時に画像が表示される前にロードを完了する。
無限スクロール
無限スクロールのコード例を示す。
const sentinel = document.getElementById('sentinel')!;
const observer = new IntersectionObserver(async ([entry]) => {
if (entry.isIntersecting) {
const items = await fetchNextPage();
renderItems(items);
}
});
observer.observe(sentinel); // リストの末尾に配置した要素を監視
スクロール連動アニメーション
スクロール連動アニメーションのコード例を示す。
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('fade-in');
}
});
}, { threshold: 0.2 });
document.querySelectorAll('.section').forEach(el => observer.observe(el));
.section { opacity: 0; transform: translateY(30px); transition: all 0.5s ease; }
.section.fade-in { opacity: 1; transform: translateY(0); }
React での使い方
React での使い方のコード例を示す。
function useInView(ref: RefObject<HTMLElement>) {
const [inView, setInView] = useState(false);
useEffect(() => {
if (!ref.current) return;
const observer = new IntersectionObserver(([entry]) => setInView(entry.isIntersecting));
observer.observe(ref.current);
return () => observer.disconnect();
}, [ref]);
return inView;
}
オプション
主なオプションを以下にまとめる。
| オプション | 説明 |
|---|---|
root | 交差を判定するコンテナ (デフォルト: ビューポート) |
rootMargin | root の余白 ('200px' で手前から検知)。単位は px と % のみで、em / rem は指定できない |
threshold | 発火させる交差率。境界を上下どちらの方向に跨いでも発火する。配列で複数指定できる ([0, 0.5, 1]) |
Intersection Observer の理解を深めるには関連書籍が参考になる。
この記事は役に立ちましたか?
関連用語
画像最適化
Web ページの画像サイズを削減し、表示速度と Core Web Vitals を向上させる技術群
CLS
ページの読み込み中に発生する予期しないレイアウトのずれを測定する Core Web Vitals の指標
遅延読み込み
リソースを必要になるまで読み込まず、初期表示速度とパフォーマンスを向上させる手法
イベントループ
Node.js のシングルスレッドで非同期 I/O を実現する実行モデル
Web Worker
ブラウザのメインスレッドとは別のスレッドで JavaScript を実行し、UI のブロッキングを防ぐ仕組み
goroutine
Go の軽量スレッドで、数十万規模の並行処理を低コストで実現する