モーダルやハンバーガーメニューを開いたときに、背景を絶対に動かしたくないとき、ありますよね。
スマホや、特にiOsではbody
やhtml
タグにoverflow: hidden;
を設定しても動いてしまうことがあるので、それだと対応できない…というときに使えるコードです。
私も先方さんからモーダルの背景を動かしたくないと要望があった際にはこのコードを使用しています。
今回はモーダルの例ですが、開くボタンや閉じるボタンを任意のクラスに変更すれば、ハンバーガーメニューでも使えるコードです。
また、今回はhtml
にscroll-behavior: smooth;
が設定されている前提のコードを書いていますが、もしなければjsのそのコードに関する部分は削除して問題ありません。
では、早速行ってみましょう!
まずはコードから。
See the Pen Untitled by えり (@anyxfbuk-the-scripter) on CodePen.
汎用性を高めるためにかなりシンプルな見た目にしているので、各々で用途に合わせてデザインは調整してください。
背景が固定されているのが確認できればOKです。
解説
html
<a data-target="modal01" class="js-modal-open">モーダル開くボタン</a>
<div class="js-modal" id="modal01">
<div class="js-modal-inner">
<p>モーダルの中身モーダルの中身モーダルの中身モーダルの中身モーダルの中身モーダルの中身モーダルの中身モーダルの中身</p>
<button type="button" class="js-modal-close">閉じる</button>
</div>
</div>
まずモーダルを開くボタンにはjs-modal-open
クラスをつけて、開きたいモーダルのdata-target
を設定しておきます。(今回はmodal01
)
モーダルにはjs-modal
クラス、その中にjs-modal-inner
クラスをつけた親要素を作って中身を入れ、閉じるボタンにはjs-modal-close
クラスをつけます。今回はボタンタグで作成していますが、js-modal-close
クラスがついていれば何の要素でもOKです。
css
.js-modal {
display: none;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
text-align: center;
overflow: auto;
z-index: 10001;
cursor: pointer;
}
.js-modal-bg {
display: none;
position: fixed;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
z-index: 10000;
cursor: pointer;
}
.js-modal-inner {
background: #fff;
width: 90%;
margin: 30px auto;
}
js-modal
display: none;
: モーダルはデフォルトで非表示です。jsを使用して表示/非表示を切り替えます。position: fixed;
: モーダルは固定され、ページをスクロールしても常に画面の同じ位置に表示されます。top: 0;
,right: 0;
,bottom: 0;
,left: 0;
: モーダルが画面全体を覆うように、四隅すべてを画面の端に揃えます。width: 100%;
: モーダルの幅は画面全体をカバーします。text-align: center;
: モーダル内のテキストや要素の中央揃え。なくてもOKoverflow: auto;
: モーダル内のコンテンツが画面を超える場合、スクロール可能になります。z-index: 10001;
: モーダルは他の要素の上に表示。cursor: pointer;
: クリック可能。
今回は.js-modal
がz-index
で上、.js-modal-bg
が下という位置関係になっているので、.js-modal-bg
をクリックすることができません。なので、.js-modal
をクリックしてもモーダルが閉じるような挙動になっているため、cursor: pointer;
を入れています。ただし、.js-modal-inner
部分はクリックしても閉じないようにjsで制御しているため、動き的には問題ないかなと。
js-modal-bg
display: none;
: 背景もデフォルトで非表示です。JavaScriptで表示を切り替えます。position: fixed;
: 背景も固定され、モーダル同様に画面全体に表示されます。top: 0;
,right: 0;
,left: 0;
,bottom: 0;
: 背景は画面全体を覆います。background: rgba(0, 0, 0, 0.8);
: 半透明の黒い背景が適用され、モーダルの背後に表示されます。色は各々調整してください。z-index: 10000;
: モーダルの背後にあるため、z-index
はモーダルよりも小さい値(10000)が設定されています。cursor: pointer;
: 背景部分もクリック可能であることを示します。
js-modal-inner
背景色やwidth
、margin
は今回用に適当につけているだけなので、各々で適宜変更してください。特に必須のcssはありません。
js
コードの流れを見ていきましょう。
1. $(function() {...})
$(function() {
ドキュメントが完全に読み込まれた後に実行される関数。おまじない。書いとけばOK
2. $(".js-modal-open").click(function() {...})
$(".js-modal-open").click(function() {
クラス .js-modal-open
がクリックされたときにモーダルを開く処理を行う。
3. $("html").css("scroll-behavior", "auto");
$("html").css("scroll-behavior", "auto"); // スムーズスクロールを一時的に無効化
スムーススクロール(scroll-behavior: smooth;
)が設定されている場合、一時的に無効化します。これをしておかないとモーダルを閉じるときの動きが変になるので…
もし最初からhtml
にスムーススクロールを設定していない場合は書かなくてOKです。
4. $("body").append('<div class="js-modal-bg"></div>');
$("body").append('<div class="js-modal-bg"></div>'),
モーダルの背景(黒い半透明部分)を動的に生成して、<body>
要素の末尾に追加しています。jsで生成するのでhtmlには書かなくてOK
5. $(".js-modal-bg").fadeIn("slow");
$(".js-modal-bg").fadeIn("slow");
.js-modal-bg
(背景)をゆっくりフェードインで表示します。slow
の代わりに数字を入れれば秒数を指定することもできますよ。
6. var e, o = "#" + $(this).attr("data-target");
var e, o = "#" + $(this).attr("data-target");
- クリックされた要素(
this
)から、data-target
属性に指定された値を取得し、変数o
に保存します。これは、開くべきモーダルのIDを指定しています。もしモーダルではなくハンバーガーメニューの場合、ここは不要です。 e
には現在のスクロール位置($(window).scrollTop()
)が保存されます。モーダルを開いたときに現在のスクロール位置を記録し、閉じたときに元の位置に戻すために使います。
7. $("body , html").css({...});
e = $(window).scrollTop();
$("body , html").css({
position: "fixed",
width: "100%",
top: -1 * e
}),
ここが今回の肝!このコードで背景固定をしてます。
モーダルが表示されている間、body
や html
要素のスクロールを固定します。具体的には、以下のようにスタイルを設定しています。
position: fixed;
: スクロールを固定し、ページが動かないようにします。width: 100%;
: 横幅が100%になるように設定します。top: -1 * e;
: スクロールした位置がずれないように、ページの表示位置を元のスクロール位置に固定します(e
は前に記録したスクロール位置です)。
position: fixed;
を使用することで、overflow: hidden;
だと効かないスマホ等でも、背景を固定することができます。
8. $(o).fadeIn("slow");
$(o).fadeIn("slow"),
取得したモーダル要素をフェードインで表示します。ここもslow
の代わりに数字を入れれば秒数指定できます。
9. $(".js-modal-bg, .js-modal-close").off().click(function() {...});
$(".js-modal, .js-modal-bg, .js-modal-close").off().click(function() {
背景やモーダルを閉じるためのボタン(.js-modal-close
)をクリックしたときに、モーダルを閉じる処理を実行します。off()
を使って以前に設定されたイベントハンドラを解除し、クリックイベントを上書きします。
10. $(o).fadeOut("slow", function() {...});
$(o).fadeOut("slow", function() {
// 背景のスクロールを元に戻す
$("body, html").css({
position: "",
width: "",
top: ""
});
$(window).scrollTop(e); // 元のスクロール位置に戻す
$(".js-modal-bg").fadeOut("slow", function() {
$(".js-modal-bg").remove();
});
モーダルをフェードアウトで非表示にします。フェードアウトが完了したら、以下の処理が行われます。
$("body, html").css({...});
: モーダルを閉じた後、body
やhtml
のposition
やtop
スタイルをリセットして、ページを通常通りスクロール可能に戻します。$(window).scrollTop(e);
: 記録しておいたスクロール位置e
にページを戻します。$(".js-modal-bg").fadeOut("slow", function() {...});
: 背景をフェードアウトし、フェードアウトが完了したら.js-modal-bg
要素を削除します。
11. setTimeout(function() {...}, 1000);
// 0.5秒後に scroll-behavior を追加
setTimeout(function() {
$("html").css("scroll-behavior", "");
}, 1000);
});
}),
モーダルが閉じた0.5秒後に、scroll-behavior
を元に戻してスムーズスクロールを再び有効にします。この遅延は、モーダルが完全に閉じられてからスムーススクロールを復帰させるためです。すぐに戻すといったんページが上に戻ってから再度元の位置に戻るというバグっぽい動きになっちゃうので…
最初からhtml
にスムーススクロールを指定していなければここは不要です。
12. $(".js-modal-inner").on("click", function(e) {...});
$(".js-modal-inner").on("click", function(e) {
e.stopPropagation()
})
})
})
モーダルの内側(.js-modal-inner
)をクリックしてもモーダルが閉じないように、e.stopPropagation()
を呼び出してイベントの伝播を止めています。
全体コード
$(function() {
$(".js-modal-open").click(function() {
$("html").css("scroll-behavior", "auto"); // スムーズスクロールを一時的に無効化
$("body").append('<div class="js-modal-bg"></div>'),
$(".js-modal-bg").fadeIn("slow");
var e, o = "#" + $(this).attr("data-target");
e = $(window).scrollTop();
$("body , html").css({
position: "fixed",
width: "100%",
top: -1 * e
}),
$(o).fadeIn("slow"),
$(".js-modal, .js-modal-bg, .js-modal-close").off().click(function() {
$(o).fadeOut("slow", function() {
// 背景のスクロールを元に戻す
$("body, html").css({
position: "",
width: "",
top: ""
});
$(window).scrollTop(e); // 元のスクロール位置に戻す
$(".js-modal-bg").fadeOut("slow", function() {
$(".js-modal-bg").remove();
});
// 0.5秒後に scroll-behavior を追加
setTimeout(function() {
$("html").css("scroll-behavior", "");
}, 1000);
});
}),
$(".js-modal-inner").on("click", function(e) {
e.stopPropagation()
})
})
})
まとめ
モーダルやハンバーガーメニューで使える、背景固定用のコードでした。
position: fixed;を使用しているところが肝ですね。ぜひ使ってみてください。
もしハンバーガーメニュー用にも実装してほしい等要望があればコード書こうと思います。
もしわからないことあれば質問ください!
記事にしてほしい・解説してほしい内容のリクエストもお待ちしています。