JavaScriptで書ける基本中の基本のギミックにモーダルウィンドウという効果があります。画像をクリックすると全画面で黒背景に大きな画像が表示され、視認性をよくするためのあれです。
HTMLコーダーにはモーダル、と聞くとアレルギー反応を起こす人がちらほらいます。実際に同僚は「モーダル……うーん、1週間ぐらいかかりますかね」と返答してしまっていたことがあります。もちろん他の業務と兼ね合いがあって1週間かかる、という意味合いなのかもしれませんが、実際のところ半日あれば十分なくらい簡単です。そう、IEが無くなった今……(IEが無くなってメリットしかないという記事は『IE(InternetExplorer)のサポート終了から生まれるメリット5選!~フロントエンドエンジニアの観点からJavaScriptを考える』)を見て見てください。
最もシンプルなモーダルのイメージはこちらです。ボタンをクリックしてみてください。
これをベースに肉付けしていけば完成に近づいていくので、どんどんカスタマイズしてしまいましょう。
トリガー(発火するポイント)を決めよう
トリガーと聞いてなんとなくイメージがつかめているのであればそれで全然OKです。ユーザーがどのような操作をしたときにモーダルが出現するか、を決めることから始めましょう。
画像をクリックした時でしょうか?記事のブロックをクリックした時でしょうか?少しトリッキーに特定の要素までスクロールしたら下からフワっ表示するような時でしょうか?
今回は最も簡単なボタンを押したときにモーダルウィンドウが表示される方法を一緒に考えてみましょう。
話はそれますが、実はクリック時にJavaScriptの機能を開始させるonclick=""
はどんなタグにも付けることが出来ます。もしかしたら<a>
や<ibutton>
タグがメジャーなので<a>
タグにしか付けられないと思いもんでいる方もいるかもしれませんので、念のため。
HTMLの構造はどうしようか
HTMLの構造にはとくには誓約はなく、CSSでモーダルの大元をposition: fix;
してしまえば画面を全部覆うことが出来ます。
1点、注意点としては、A:モーダルの土台(実務ではwrapラップと呼ぶことが多いです)、B:モーダルの中のコンテンツの2つのDOMは、親子関係にしておいた方が無難です。親子関係以外でももちろん実装できますが、要素の移動の手間を考えると親子要素がベストかなと思います。
では実際のHTML構造のサンプルを見て見ましょう。
<body>
<h2>Modal Window Example</h2>
<button onclick="openModal()">Open Modal</button>
<div id="modal" class="modal is-hidden">
<div class="modal-content">
<span class="close" onclick="closeModal()">×</span>
<p>This is a modal window.</p>
</div>
</div>
</body>
念のためですが、説明しておくと大枠はid="modal"
の<div>
ですね。
そしてモーダルに含まれるコンテンツはdiv.modal-content
になります。
※トリガーは<h2>
直下のOpen Modalボタンです。onclick="openModal()"
(後述)が実行された時にモダールが表示されます。(注意:もちろんこのコード単体ではモーダルになりません。)
このサンプルもA:モーダルの土台 > B:モーダルというDOM構造にしました。
ここで重要なんですが、A:モーダルの土台の<div>にクラスで「is-hidden
」を付けておくようにしてください。後でCSSについて書きますが、これは初期表示はhidden(隠す・消す)ためです。
CSSのでレイアウトを先に組んでしまう方が効率的かと思います。
#modal {
display: none;
position: fixed;
z-index: 2;
left: 0;
top: 0;
width: 100vw;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.5);
}
#modal .modal-content {
background-color: #fefefe;
margin: 30vh auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 400px;
}
#modal.is-hidden {
display: none;
}
#modal.is-shown {
display: block;
}
#modal .close {
color: #aaa;
float: right;
font-size: 100%;
font-weight: bold;
}
#modal .close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
JavaScriptでドキドキしないように
ここまでくればほぼ終わりです。JavaScriptの役目は大したことないのでさらっと行きましょう。こういったHTML+CSS+JSが絡むギミックについては出来るだけJavaScriptを簡素化することがコツです。
メリットとしては、JavaScriptを知らなくてもCSSでカバーできるメンバーがいるはずなので修正はCSSだけで行うことが出来る。
また、JavaScriptゴリゴリのコードを実行させるよりもCSSで調整したほうが、ページ速度としても早いです。CSSって非常に重要で意外と万能です。(IEが無くなった今!)
ここでは、JavaScriptをの役目をclass
を付けるだけに制限してしまいましょう。下記がコードです。難しくありません。
// モーダルウィンドウを開く関数
function openModal() {
let modal = document.getElementById("modal");
modal.classList.remove("is-hidden");
modal.classList.add("is-shown");
}
// モーダルウィンドウを閉じる関数
function closeModal() {
let modal = document.getElementById("modal");
modal.classList.remove("is-shown");
modal.classList.add("is-hidden");
}
実際はもっと、その要素に付与しようとしているclassが存在するのかなどのチェックをする分岐をいれたりしますが、とりあえず動作はするのでこれでいいかなともいます。無理してif文やらぶっこんでもスパゲッティが出来上がるのでその後食べるのが大変になります。(システム界のスパゲッティは激マズです)
モーダル表示がうまくいかないことが多い要注意パターン
以上のソースコードである程度はモーダルっぽい動きになるのですが、ちょっと気を付けなければならないことやハマリがちなポイントを解説しておきます。
モーダルが全画面に被さらない(z-indexが効かない)
これ、知らないと結構ハマります。先ほどのdiv#modal
のDOMの記述「位置」について触れませんでしたが、結構重要で表示するモーダル自体のHTML群をどこに書くべきかはほぼ100%の確率で<bod>
の直下(階層的に)です。この「階層的に」にもあるようにHTMLファイル内の<body>の真下に書くのではなくて、セレクタで表現するとbody > #modal
という意味です。
ではなぜbody > #modal
としなくてはいけないのか?その理由は、重なり順が思い通りにいかないことがほぼほぼ毎回あるからです。
たとえばこのページでも同じ現象が起こります。次のボタンを押してみてください。
アドセンスのバナーや筆者の髭顔やバナーなどがモーダルの上に出てしまってます、、、これを回避るするためにbody > #modal
で対処するんですね。これはz-indexの不具合(?)でDOM階層が深いとどんなにz-indexを上げても最上位に表示することが出来ないためなのです。是非覚えておいてください。
「ちょっと待って、これWordpressの投稿記事だからbody > #modal出来ないんだけど!」という方、ご安心ください。下記のコードで解決します。
$(function(){
$('#modal').appendTo('body');
});
面倒なのでjQueryで書いちゃってますが、必要があればJavaScriptで書いても大丈夫です。#modal
をbody > #modal
に移動するスクリプトですね。
マウスホイールすると背後のコンテンツがスクロールする問題
これまでサンプルコードを明示してきましたが、このモーダルの下にあるコンテンツがスクロールしちゃうんです。それでもOKというクライアントやディレクターもいるにはいるのですが、どちらかというとNGと言い出す不届きものの方が多いです。
では、どうやったら背後のコンテンツをFix出来るでしょうか、Fix?Fix?あ、Fixか!
ではなく、overflow: hidden;
1つでもいけます。
body.js-now-modal {
overflow: hidden;
}
// モーダルウィンドウを開く関数
function openModal() {
let modal = document.getElementById("modal");
let _body = document.getElementsByTabName('body')[0];
modal.classList.remove("is-hidden");
modal.classList.add("is-shown");
_body.classList.add("js-now-modal");
}
// モーダルウィンドウを閉じる関数
function closeModal() {
let modal = document.getElementById("modal");
let _body = document.getElementsByTabName('body')[0];
modal.classList.remove("is-shown");
modal.classList.remove("is-hidden");
_body.classList.remove("js-now-modal");
}
CSSはこんな感じでbodyにクラスがついたらoverflow: hidden;
にします。JavaScriptは先ほどのモーダル用のJSに追記すればOKです。表示位置はCSSをいい感じに調整すればOKですね。