要素がスクロールで画面内(ビューポート内)に入ったかを判別できると、画像の遅延読み込みやスクロールアニメーションなどに応用できます。判定には大きく2つの方法があります。
IntersectionObserver(高速・推奨)。「今この瞬間に見えているか」をその場で知りたいなら getBoundingClientRect()。なお、「完全に見える」と「一部でも見える」では判定条件が違うので注意します。方法1:IntersectionObserver(推奨)
IntersectionObserver は、要素がビューポートに入った・出たタイミングを効率的に監視します。スクロールイベントを自分で張る必要がなく、高速です。
const target = document.querySelector(".target");
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log("画面内に入りました");
} else {
console.log("画面外に出ました");
}
});
});
observer.observe(target);
「何%見えたら」を指定する threshold や複数要素の監視など、詳しくはIntersectionObserverで複数要素を監視する方法で解説しています。
方法2:getBoundingClientRectで判定する
getBoundingClientRect() は、要素の今の画面上の位置(top / bottom / left / right)を返します。これをビューポートの大きさと比べれば、その瞬間に画面内かどうか分かります。
function isFullyInViewport(el) {
const rect = el.getBoundingClientRect();
const vh = window.innerHeight || document.documentElement.clientHeight;
const vw = window.innerWidth || document.documentElement.clientWidth;
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= vh &&
rect.right <= vw
);
}
「完全に見える」と「一部でも見える」の違い
上の関数は要素が丸ごと画面に収まっているかの判定です。しかし遅延読み込みやアニメーションでは、一部でも見えたら処理したいことが多いです。その場合は条件が変わります。
function isPartiallyInViewport(el) {
const rect = el.getBoundingClientRect();
const vh = window.innerHeight || document.documentElement.clientHeight;
// 縦方向:上端が画面下より上 かつ 下端が画面上より下
return rect.top < vh && rect.bottom > 0;
}
・完全に見える:
top >= 0 && bottom <= 画面高さ・一部でも見える:
top < 画面高さ && bottom > 0用途に合わせて選びましょう。「下半分だけはみ出した要素」は、前者ではfalse、後者ではtrueになります。
動くデモ:スクロールで判定が変わる様子
↓ 枠の中をスクロールしてターゲットを動かすと、「一部でも見える/完全に見える」の判定がリアルタイムに変わります(色:グレー=見えない/青=一部/緑=完全。判定は枠を基準にした相対版です):
「下半分だけはみ出した」状態では一部=true・完全=falseになるのが確認できます。この違いが、遅延読み込みやアニメーションの発火タイミングを左右します。
IntersectionObserver の threshold でも表せます。threshold: 0(既定)=少しでも見えたら(=一部でも見える)、threshold: 1=完全に見えたら発火します。getBoundingClientRect の2つの条件式は、IntersectionObserver の threshold に対応していると考えると分かりやすくなります。scrollイベントで判定するときの注意
getBoundingClientRect() を scroll イベントで毎回呼ぶと、1回のスクロールで何十回も実行されて重くなります。スクロール連動で使うなら、requestAnimationFrame で間引くか、そもそも IntersectionObserver を使うのがおすすめです。
let ticking = false;
window.addEventListener("scroll", () => {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
if (isPartiallyInViewport(target)) {
console.log("見えています");
}
ticking = false;
});
});
どちらを使う?(使い分け)
- 入った・出たを監視して処理を発火(遅延読み込み・スクロールアニメ)→
IntersectionObserver - 今この瞬間に見えているかをその場で知りたい(クリック時のチェックなど)→
getBoundingClientRect()
スクロールで要素をふわっと表示する具体例はIntersectionObserverでふわっと表示させる方法、画像の遅延読み込みはIntersection Observerを使った画像の遅延読み込み方法を参考にしてください。
よくある質問(FAQ)
element.getBoundingClientRect() でビューポート基準の座標を取得し、top >= 0 && bottom <= window.innerHeight(完全に見える場合)で判定します。入った・出たを監視するなら IntersectionObserver がよりスマートです。rect.top < window.innerHeight && rect.bottom > 0 で判定します。「完全に見える」(top >= 0 && bottom <= 画面高さ)とは条件が異なるので、用途に合わせて選びます。getBoundingClientRect() を比較します。要素の top / bottom / left / right がコンテナの範囲内に収まっているかをチェックします。IntersectionObserver が最も高速です。コールバックの entry.isIntersecting で交差を確認して処理します。一度きりなら observer.unobserve(entry.target) で監視を解除します。まとめ
要素が画面内かどうかの判別は、「入った・出た」を監視するなら IntersectionObserver、「今見えているか」をその場で知りたいなら getBoundingClientRect() を使います。
注意点は、「完全に見える」と「一部でも見える」で条件が違うこと、そして scroll で getBoundingClientRect() を使うならrequestAnimationFrame で間引くことです。迷ったら、パフォーマンスの高い IntersectionObserver を選びましょう。

