JavaScript | DOM 操作:要素の位置・サイズ・スクロール – スクロール固定(overflow: hidden)

JavaScript JavaScript
スポンサーリンク

スクロール固定(overflow: hidden)とは何か

「スクロール固定」は、ユーザーがスクロールできない状態にすることです。最も簡単な方法は、対象要素(よくあるのは body)に overflow: hidden を適用すること。ここが重要です:ページ全体を止めたいのか、特定の領域だけを止めたいのかで“どこに適用するか”が変わります。また、スクロールバーの有無やモバイルの挙動に配慮しないと見た目や操作性が崩れます。


ページ全体のスクロールを止める基本(body へ適用)

最小の例(モーダル表示中だけスクロール固定)

<button id="open">モーダルを開く</button>
<div id="backdrop" style="position:fixed; inset:0; background:rgba(0,0,0,.4); display:none"></div>
<div id="modal" style="position:fixed; left:50%; top:50%; transform:translate(-50%,-50%); display:none; background:#fff; padding:16px; max-width:min(92vw,640px); max-height:84vh; overflow:auto">
  モーダルコンテンツ<br>(長文でも内側だけスクロール)
  <button id="close">閉じる</button>
</div>
<script>
  const open = document.getElementById("open");
  const close = document.getElementById("close");
  const backdrop = document.getElementById("backdrop");
  const modal = document.getElementById("modal");

  function lockBodyScroll(lock) {
    document.body.style.overflow = lock ? "hidden" : "";
  }
  function openModal() {
    lockBodyScroll(true);
    backdrop.style.display = "block";
    modal.style.display = "block";
    modal.focus();
  }
  function closeModal() {
    lockBodyScroll(false);
    backdrop.style.display = "none";
    modal.style.display = "none";
  }
  open.addEventListener("click", openModal);
  close.addEventListener("click", closeModal);
</script>
HTML

ここが重要です:ページ全体を止めるときは body に overflow: hidden。長いモーダルは modal 自体を overflow: auto にして“内側だけスクロール”させると体験が安定します。


スクロールバーによるレイアウトズレの対策(補正を入れる)

スクロール固定で横揺れさせない(バー幅分を補填)

<script>
  function getScrollbarWidth() {
    return window.innerWidth - document.documentElement.clientWidth;
  }
  function lockBodyScroll(lock) {
    if (lock) {
      const w = getScrollbarWidth();        // 例:Windows で 17px
      document.body.style.overflow = "hidden";
      document.body.style.paddingRight = w + "px"; // レイアウトの横ズレ防止
    } else {
      document.body.style.overflow = "";
      document.body.style.paddingRight = "";
    }
  }
</script>
HTML

ここが重要です:overflow: hidden にすると縦スクロールバーが消え、内容が横に数ピクセル動きます。clientWidth と innerWidth の差(バー幅)を右パディングに加えると“ぴたっと”揺れを防げます。


モバイル特有の注意点(iOS のスクロール固定とタッチ)

iOS Safari では body を止めても“内側が動く”問題への対処

<script>
  // モーダル内以外のタッチ移動を抑止(モーダル中のみ)
  const modal = document.getElementById("modal");
  function enableIOSLock() {
    document.addEventListener("touchmove", preventOutside, { passive: false });
  }
  function disableIOSLock() {
    document.removeEventListener("touchmove", preventOutside, { passive: false });
  }
  function preventOutside(e) {
    if (!modal.contains(e.target)) e.preventDefault(); // 背景のタッチスクロールを無効化
  }
</script>
HTML

ここが重要です:iOS は body の overflow: hidden だけでは十分でないことがあります。背景(モーダル外)の touchmove を preventDefault して補助すると、確実にページスクロールを止められます。内側スクロールは modal を overflow: auto にして許可します。


代替・補助のテクニック(位置固定・オーバースクロール制御)

スクロール位置を保持して “見かけの固定”にする(位置保存+固定)

<script>
  let savedY = 0;
  function hardLock(lock) {
    if (lock) {
      savedY = window.scrollY;
      document.body.style.position = "fixed";
      document.body.style.top = -savedY + "px";
      document.body.style.width = "100%";
    } else {
      document.body.style.position = "";
      document.body.style.top = "";
      document.body.style.width = "";
      window.scrollTo({ top: savedY });
    }
  }
</script>
HTML

ここが重要です:position: fixed を body に当てる方法は“確実に動かない”反面、レイアウトや position: fixed の子要素に影響することがあります。素性の違いを理解して、overflow: hidden と使い分けます。

背景の引っ張り・端でのゴム挙動を抑える(overscroll-behavior)

html, body { overscroll-behavior: none; }
CSS

ここが重要です:モバイルで端までスクロールしたときの“引き”を防げます。モーダル中の背景に不穏な動きが出るのを抑える補助として有効です。


領域限定のスクロール固定(特定パネルだけを止める)

横スクロール専用領域で縦スクロールを止める

<div id="row" style="overflow:auto; white-space:nowrap; height:160px; border:1px solid #ccc">
  <span>カード1</span><span>カード2</span><span>カード3</span><span>カード4</span>
</div>
<script>
  // 縦スクロールは抑止し、横だけ許可
  row.addEventListener("wheel", (e) => {
    if (Math.abs(e.deltaX) < Math.abs(e.deltaY)) {
      e.preventDefault();                 // 縦方向のホイールを止める
      row.scrollLeft += e.deltaY;         // 縦ホイールを横移動に変換
    }
  }, { passive: false });
</script>
HTML

ここが重要です:特定の領域だけを“意図した軸”で動かすと、誤スクロールを防げます。止めるハンドラでは必ず { passive: false } にします。


アクセシビリティ・操作性まで整える(フォーカス・トラップ・閉じ方)

モーダル中はフォーカスを内側に閉じ込める

<div id="modal" role="dialog" aria-modal="true" tabindex="-1">…</div>
<script>
  function trapFocus(e) {
    const open = modal.style.display === "block";
    if (open && !modal.contains(e.target)) {
      e.stopPropagation();
      modal.focus();
    }
  }
  document.addEventListener("focusin", trapFocus, { capture: true });
</script>
HTML

ここが重要です:スクロール固定は“視覚の中心”を作る操作です。フォーカスも中央(モーダル内)に留めることで、キーボード操作でも破綻しません。Esc で閉じる導線も必ず用意しましょう。


よくある落とし穴と回避策

スクロールバー消失によるレイアウト揺れ

固定時はバー幅ぶん右パディングで補正。解除時に忘れずにクリアします。揺れは“違和感”の元なので最初から対策を入れます。

iOS で“止まらない”/背景が動く

body の overflow: hidden だけでは足りないことがあります。背景タッチの preventDefault を併用し、モーダル内だけ overflow: auto にして自然な操作を保ちます。

全域でスクロールを無理に止めて UX を損ねる

本当に必要な場面に限定し、閉じる方法(ボタン・外クリック・Esc)を複数用意。長文はモーダル内スクロールへ逃がし、可視範囲に余白を残す設計にします。

固定方式の副作用(position: fixed を body に)

フォームのオートコンプリートポップアップや position: fixed 子要素がズレることがあります。最初は overflow: hidden で実装し、難しいケースのみ固定方式に切り替えます。


まとめ

スクロール固定は「どこを止めるか」と「副作用の制御」が鍵です。ページ全体なら body に overflow: hidden を適用し、バー幅補正で横揺れを防ぐ。モバイル(特に iOS)では背景の touchmove を抑止して確実に固定し、長いコンテンツはモーダル内の overflow: auto で逃がす。必要に応じて overscroll-behavior や位置固定(body を fixed 化)を使い分け、フォーカス制御と閉じ方まで整える。これらを押さえれば、初心者でも“止めるべきときにだけ、気持ちよく止める”スクロール固定を堅実に実装できます。

タイトルとURLをコピーしました