スクロール対応のモーダルウィンドウの作成

個人的な使いまわしように作った Twitter ライクなモーダルウィンドウのメモです。

元の画面の高さよりも縦幅が長いウィンドウにスクロールで対応するようにします。

デモ

ウィンドウと開くリンクのHTMLの記述

<!--ウィンドウ本体-->
<div id="ModalWindow1" class="modalwindow">
    <div class="body">
        <div class="modal_content">
            内容
        </div>
        <footer style="text-align:center">
            <button data-closemodal="ModalWindow1" class="close_modal">閉じる</button>
        </footer>
    </div>
</div>
<!--開くボタン-->
<button class="open_modal" data-openmodal="ModalWindow1">普通に開く</button>

.modalwindowがオーバーレイとスクロール領域の役割をするレイヤーで、その中の.bodyが実際のウィンドウです。
開くためのリンクはdata-openmodal="[ウィンドウのID]"でターゲットのウィンドウのIDを指定しています。.modalwindow毎にIDを変える事で複数のウィンドウが設置可能です。

JavaScriptの本体コード

前提で jQuery を使用しています。

$(function() {
    // スクロールバーの幅を取得して調整するためのCSSをヘッダーに追加
    var width = window.innerWidth - document.body.clientWidth;
    var newStyle = document.createElement('style');
    document.head.appendChild(newStyle);
    newStyle.sheet.insertRule('body.enabled_modal { overflow: hidden; }', 0);
    newStyle.sheet.insertRule('body.enabled_modal #Main, body.enabled_modal #Header, body.enabled_modal #Footer, body.enabled_modal .fixedlay { padding-right: ' + (width) + 'px; }', 1);

    // モーダルウィンドウを開く処理
    $(document).on('open', '.modalwindow', function() {
        $(this).addClass('is_visible').show().animate({
            opacity: 1
        }, 200).scrollTop(0);
        $('body').addClass('enabled_modal');
    });

    // モーダルウィンドウを閉じる処理
    $(document).on('close', '.modalwindow:not(.lock)', function() {
        $(this).removeClass('is_visible').animate({
            opacity: 0
        }, 200, function() {
            $(this).hide();
            $('body').removeClass('enabled_modal');
        });
    });

    // data-openmodalをクリックした時 → 開く処理
    $(document).on('click', '[data-openmodal]', function(e) {
        var targetID = $(this).attr('data-openmodal');
        $('#' + targetID).trigger('open');
    });

    // data-closemodalをクリックした時 → 閉じる処理
    $(document).on('click', '[data-closemodal]', function(e) {
        var targetID = $(this).attr('data-closemodal');
        $('#' + targetID).trigger('close');

    });

    // オーバーレイをクリックした時 → 閉じる処理
    $(document).on('click', '.modalwindow:not(.no_overlay_close)', function(e) {
        if (e.currentTarget === e.target) {
            $(this).trigger('close');
        }
    });
});

まず最初にスクロールバーを調整するための CSS を動的に追加しています。

body にスクロールバーがあった場合にウィンドウを開くとスクロールバーが2重で表示されてしまうので、ウィンドウが開かれた=bodyに.enabled_modalが付いた場合に body のスクロールバーを非表示にし、その分の隙間を調整するための CSS を追加しておきます。

ウィンドウ全体の幅から表示領域を引いた分でスクロールバーの横幅が取得できるので、サンプルのソースコードの場合は背景の色が途切れないようにその分をヘッダー、フッター、メインのラッパーレイヤーにpadding-rightを付けて埋めています。右側に余白が出来てもいいなら body に直接padding-rightを指定してもいいですし、デザインによって CSS の書き方はここで調整します。基本的には一番外側のラッパーだけを指定すればいいのですが、fixedのレイヤーはラッパーのポジション影響外になるためラッパーと同じ処理が必要です。

ウィンドウの開き方&閉じ方

基本はdata-openmodal="[ウィンドウのID]"が付いている要素をクリックで開きdata-closemodal="[ウィンドウのID]"が付いている要素をクリックで閉じます。
それ以外のイベントや、スクリプトから動的に開きたい場合は$([ID]).trigger('open')$([ID]).trigger('close')を使います。

$('#SampleForm').on('submit', function(e) {
e.preventDefault();
$([ID]).trigger('open');
/* なにか処理 */
});

最近 document にバインドしすぎなので実行数が少ないなら直接 modalwindow にバインドさせちゃったほうが良いのかも。

CSSの記述とポイント

動的コンテンツ対策のため、body のスクロールバーは元から表示させておきます。

body {
  overflow-y: scroll;
}
@charset "UTF-8";
/* 開く時の.bodyのアニメーション */
@keyframes modalwindow_body_in {
  0% {
    transform: translateY(-20px);
  }
  100% {
    transform: translateY(0px);
  }
}

@keyframes modalwindow_body_out {
  0% {
    transform: translateY(0px);
  }
  100% {
    transform: translateY(-20px);
  }
}

.modalwindow {
  /* オーバーレイ&スクロール領域になるレイヤー */
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  overflow: auto;
  z-index: 101;
  width: 100%;
  /* スマホでスワイプして指を離すまでの間下部の背景が無くなるため余分に指定 */
  height: calc(100% + 100px);
  background: rgba(0, 0, 0, 0.5);
  border-bottom: 100px solid transparent;
  cursor: pointer;
  opacity: 1;
}

.modalwindow.no_overlay_close {
  cursor: default;
}

.modalwindow.lock .close_modal {
  background: #ccc;
}

.modalwindow .body {
  /* ウィンドウ自体 アニメーションなどで装飾 */
  position: relative;
  left: 50%;
  top: 0;
  width: 600px;
  margin-left: -300px;
  margin-top: 40px;
  margin-bottom: 10px;
  background: #fff;
  border-radius: 4px;
  cursor: default;
  box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
  transform: translateY(-20px);
  animation: 200ms modalwindow_body_out;
}

.modalwindow .body .modal_content {
  padding: 20px;
}

.modalwindow .body > footer {
  padding: 10px;
  background: #fcfcfc;
  border-radius: 0 0 4px 4px;
  border-top: 1px solid #e5e5e5;
}

.modalwindow.is_visible .body {
  transform: translateY(0px);
  animation: 400ms modalwindow_body_in;
}

.modalwindow_header_close {
  /* 右上に表示する×ボタン 面倒なので動的に追加してます */
  position: absolute;
  right: 0;
  top: 5px;
  background: none;
  border: none;
  cursor: pointer;
}

.modalwindow_header_close:before {
  content: "\D7";
  font-size: 24px;
  font-family: serif;
  color: #ccc;
}

@media screen and (max-width: 900px) {
  .modalwindow .body {
    position: relative;
    width: calc(100% - 20px);
    margin: 10px;
    left: 0;
  }
}

Ajaxとか使う

エフェクトが分かりにくいのでsetTimeoutで擬似的に遅延させています。なのでこのまま使う場合は外してください(´•ω•`)(「//実際は要らない」のところ)

$(function() {
    // ajaxとか使う場合
    $('#ModalWindow3Submit').on('click', function(e) {
        // 通信途中はウィンドウを閉じられないようににしてウィンドウを開く
        $('#ModalWindow3').addClass('lock').trigger('open');
        var modal_content = $('#ModalWindow3 .modal_content');
        // ローディング中の表示
        $(modal_content).html('<p class="loading_text">Loading now...</p>');
        // もとの高さ
        var cur_height = $(modal_content).outerHeight();
        $.ajax({
            type: 'POST',
            url: 'ajax.txt',
            dataType: 'text',
            cache: false
        }).done(function(result) {
            // 擬似的に2秒間遅延してます
            setTimeout(function() {
                var new_height = $(modal_content).html(result).outerHeight();
                $(modal_content).trigger('smresize', [cur_height, new_height]);
                $('#ModalWindow3').removeClass('lock'); // 実際は要らない
            }, 2000);
        }).fail(function() {
            $(modal_content).text('通信エラー');
            $('#ModalWindow3').removeClass('lock'); // 実際は要らない
        }).always(function() {
            // 通信が終わったらロックを解除
            //$('#ModalWindow3').removeClass('lock'); // 実際は要る
        });
    });

    // [カスタム].modal_contentの高さをスムーズに変える
    $(document).on('smresize', '.modalwindow .modal_content', function(e, cur_height, new_height) {
        var elem = this;
        $(elem).css({
            'height': cur_height,
            'opacity': 0
        }).animate({
            'height': new_height
        }, 400, function() {
            $(elem).height('auto').animate({
                'opacity': 1
            });
        });
    });
});

.modal_contentsmresizeというカスタムイベントを作成して高さが変わった時にアニメーションさせる処理をさせます。

コード中のajax.txt等は適当に通信先に変更してください。

ローディングテキストが点滅する CSS を追加します。

@charset "UTF-8";
/*  ローディング中のテキスト点滅 */
@keyframes loading_text {
  0% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}

.loading_text {
  text-align: center;
  color: #bbb;
  animation: 1s loading_text 0s infinite alternate;
}
Edited on 2017.02.03 Created on 2016.08.18 JavaScript jQuery Webデザイン Ajax