モーダル表示中に背景のスクロールを固定する方法【CSS・JavaScript】

Webサイトでモーダルウィンドウを開いたとき、背景がスクロールできてしまって困ったことはありませんか?

Webサイトでモーダルを表示する場面はよくありますが、その際に、背景(=body全体)のスクロールを固定しないと、ユーザーにとって操作しづらくなってしまうことがあります。

この記事では、以下の内容をサンプルコードを用いてわかりやすく解説します。

  • 背景のスクロール固定が必要な理由
  • 背景のスクロールを固定する方法
    • overflow: hidden;を使ってスクロールを止める
    • position: fixed;を使って背景を固定し、モーダルを開いた地点のスクロール量をズラす

背景のスクロール固定が必要な理由

モーダルを開いたときに、背景(=body全体)がスクロールできてしまうと、次のような問題が発生します。

  • ユーザーが意図せず背景をスクロールしてしまう
  • モーダル内のスクロールと競合し、誤操作が発生する

このように、スクロールが意図しない動作を生むことが多いため、モーダルの操作に集中してもらうためには、背景のスクロールはしっかり固定すべきであると考えられます。

背景のスクロールを固定する方法

overflow: hidden;を使ってスクロールを止める

もっとも一般的でシンプルな方法が、bodyoverflow: hidden;を付ける方法です。

メリット

  • 実装が非常に簡単

デメリット

  • Windowsなどでスクロールバーが常時表示の場合、スクロール固定時に画面がスクロールバーの幅の分だけ横にガタつく

サンプルコードを以下に示します。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>モーダルのスクロール固定(overflow: hidden;)</title>
    <style>
      body {
        overflow-y: scroll; /* スクロールバーを適用 */
        margin: 0;
      }

      .content {
        height: 200vh; /* スクロールできるように */
        background: lightgray;
        padding: 20px;
      }

      .modal {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 80%;
        max-width: 400px;
        background: white;
        padding: 20px;
        box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
        display: none;
        z-index: 1000;
      }

      /* bodyスクロール固定用 */
      .modal-open {
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <div class="content">
      <button onclick="openModal()">モーダルを開く</button>
    </div>

    <div class="modal">
      <p>モーダルウィンドウ</p>
      <button onclick="closeModal()">閉じる</button>
    </div>

    <script>
      function openModal() {
        document.body.classList.add('modal-open'); // bodyにスクロール固定用のクラスを追加
        document.querySelector('.modal').style.display = 'block'; // モーダルを開く
      }

      function closeModal() {
        document.body.classList.remove('modal-open'); // スクロール固定用のクラスを削除
        document.querySelector('.modal').style.display = 'none'; // モーダルを閉じる
      }
    </script>
  </body>
</html>

この方法は、実装が非常に簡単ですが、スクロール固定時に画面がスクロールバーの幅の分だけ横にガタつくのが問題です。これが気になる人はスクロールの固定にposition: fixedを使うと、ガタつき問題を解消することができます。

position: fixed;を使って背景を固定し、モーダルを開いた地点のスクロール量をズラす

もう一つの方法は、bodyにposition: fixed;を指定して画面そのものを固定する方法です。

メリット

  • スクロールバーによるガタつきを防げる

デメリット

  • window.scrollToなどを使って元のスクロール位置に戻すロジックが必要

サンプルコードを以下に示します。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>モーダルのスクロール固定(position: fixed;)</title>
    <style>
      body {
        overflow-y: scroll; /* スクロールバーを適用 */
        margin: 0;
      }

      .content {
        height: 200vh; /* スクロールできるように */
        background: lightgray;
        padding: 20px;
      }

      .modal {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 80%;
        max-width: 400px;
        background: white;
        padding: 20px;
        box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
        display: none;
        z-index: 1000;
      }

      /* bodyスクロール固定用 */
      .modal-open {
        width: 100%;
        position: fixed;
        left: 0;
      }
    </style>
  </head>
  <body>
    <div class="content">
      <button onclick="openModal()">モーダルを開く</button>
    </div>

    <div class="modal">
      <p>モーダルウィンドウ</p>
      <button onclick="closeModal()">閉じる</button>
    </div>

    <script>
      let scrollPosition = 0; // スクロール位置を保存する変数

      function openModal() {
        scrollPosition = window.pageYOffset; // 現在のスクロール位置を取得して保存
        document.body.classList.add('modal-open'); // bodyにスクロール固定用のクラスを追加
        document.body.style.top = `-${scrollPosition}px`; // topプロパティでスクロール位置を固定
        document.querySelector('.modal').style.display = 'block'; // モーダルを開く
      }

      function closeModal() {
        document.body.classList.remove('modal-open'); // スクロール固定用のクラスを削除
        document.body.style.top = ''; // topプロパティでスクロール位置を固定したのをリセットする
        window.scrollTo(0, scrollPosition); // モーダルを開く前のスクロール位置に戻す
        document.querySelector('.modal').style.display = 'none'; // モーダルを閉じる
      }
    </script>
  </body>
</html>

bodyposition: fixed;を指定すると、スクロールが完全に無効化されるため、すべてのブラウザで確実にスクロールを固定できます。しかしこのとき、bodyの中身によってはレイアウトが崩れることがあります。特に横幅が狭くなってしまう場合があるので、保険としてwidth: 100%;を併せて指定しておくのがおすすめです。

また、position: fixed;を使うと、ページのスクロール位置がリセットされ、画面が一番上に戻ってしまうという問題が発生します。これを防ぐために、モーダルを「開く時」と「閉じる時」において、以下のような対処を行う必要があります。

モーダルを「開く時」

  1. モーダルを開く前に、現在のスクロール位置を取得して保存する。
  2. position: fixed;を設定する。
  3. bodytopプロパティにtop: -スクロール量pxを指定し、見た目上は元の位置にいるように見せかける。

モーダルを「閉じる時」

  • position: fixed;を解除する。
  • bodytopプロパティでスクロール位置を固定したのをリセットする
  • window.scrollTo等でモーダルを開く前のスクロール位置に戻す

本記事のまとめ

この記事では『背景(=body全体)のスクロールを固定する方法』について、以下の内容を説明しました。

  • 背景のスクロール固定が必要な理由
  • 背景のスクロールを固定する方法
    • overflow: hidden;を使ってスクロールを止める
    • position: fixed;を使って背景を固定し、モーダルを開いた地点のスクロール量をズラす

お読み頂きありがとうございました。