【JavaScript】要素が画面内にあるかどうかを判別する方法

要素がスクロールで画面内(ビューポート内)に入ったかを判別できると、画像の遅延読み込みやスクロールアニメーションなどに応用できます。判定には大きく2つの方法があります。

この記事の結論:「入った・出た」を監視するなら IntersectionObserver(高速・推奨)。「今この瞬間に見えているか」をその場で知りたいなら getBoundingClientRect()。なお、「完全に見える」と「一部でも見える」では判定条件が違うので注意します。
スポンサーリンク

方法1:IntersectionObserver(推奨)

IntersectionObserver は、要素がビューポートに入った・出たタイミングを効率的に監視します。スクロールイベントを自分で張る必要がなく、高速です。

JavaScript: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)を返します。これをビューポートの大きさと比べれば、その瞬間に画面内かどうか分かります。

JavaScript:完全に画面内か判定
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
  );
}

「完全に見える」と「一部でも見える」の違い

上の関数は要素が丸ごと画面に収まっているかの判定です。しかし遅延読み込みやアニメーションでは、一部でも見えたら処理したいことが多いです。その場合は条件が変わります。

JavaScript:一部でも見えているか判定
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 で指定:同じ「完全/一部」は IntersectionObserverthreshold でも表せます。threshold: 0(既定)=少しでも見えたら(=一部でも見える)、threshold: 1完全に見えたら発火します。getBoundingClientRect の2つの条件式は、IntersectionObserverthreshold に対応していると考えると分かりやすくなります。

scrollイベントで判定するときの注意

getBoundingClientRect()scroll イベントで毎回呼ぶと、1回のスクロールで何十回も実行されて重くなります。スクロール連動で使うなら、requestAnimationFrame間引くか、そもそも IntersectionObserver を使うのがおすすめです。

JavaScript:requestAnimationFrameで間引く
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)

QJavaScriptで要素が画面内に表示されているか確認するには?
Aelement.getBoundingClientRect() でビューポート基準の座標を取得し、top >= 0 && bottom <= window.innerHeight(完全に見える場合)で判定します。入った・出たを監視するなら IntersectionObserver がよりスマートです。
Q「一部でも見えている」を判定するには?
Arect.top < window.innerHeight && rect.bottom > 0 で判定します。「完全に見える」(top >= 0 && bottom <= 画面高さ)とは条件が異なるので、用途に合わせて選びます。
Q要素が特定のコンテナ内に収まっているか確認するには?
A対象要素とコンテナそれぞれの getBoundingClientRect() を比較します。要素の top / bottom / left / right がコンテナの範囲内に収まっているかをチェックします。
Qスクロールで表示されたときだけ処理するには?
AIntersectionObserver が最も高速です。コールバックの entry.isIntersecting で交差を確認して処理します。一度きりなら observer.unobserve(entry.target) で監視を解除します。

まとめ

要素が画面内かどうかの判別は、「入った・出た」を監視するなら IntersectionObserver「今見えているか」をその場で知りたいなら getBoundingClientRect() を使います。

注意点は、「完全に見える」と「一部でも見える」で条件が違うこと、そして scrollgetBoundingClientRect() を使うならrequestAnimationFrame で間引くことです。迷ったら、パフォーマンスの高い IntersectionObserver を選びましょう。