概要
- スマートフォン向け。
- ボタンを押すと画面いっぱいにモーダルが表示される。
- モーダル右上の「×」ボタンで閉じる。
- 表示と非表示をCSS Animationで演出。
CSS Animation
- スマートフォンだと、
-webkit-
のプレフィックス使えば全てカバーできる状況と言っていい(Android 4以上)。 - IE9が対象外で良いなら、PCでも使える。
サンプルコード
<div class='jscContents'>
<article>
<h1 class="title">タイトル</h1>
<p>テキストテキスト</p>
<div class="jscModalTrigger btn">モーダルを開く</div>
</article>
<article>
<h1 class="title">タイトル</h1>
<p>テキストテキスト</p>
<div class="jscModalTrigger btn">モーダルを開く</div>
</article>
</div>
<div id="modal" class="jscModal modal">
<header class="modalHeader">
<h1 class="modalTitle">全画面モーダル</h1>
<div class="jscBtnClose btnClose"></div>
</header>
<ul class="modalList">
<li>Chrome</li>
<li>Firefox</li>
<li>IE</li>
<li>Opera</li>
<li>Safari</li>
</ul>
<ul class="modalList">
<li>Chrome</li>
<li>Firefox</li>
<li>IE</li>
<li>Opera</li>
<li>Safari</li>
</ul>
</div>
html,
body {
height: 100%;
min-height: 100%;
}
body {
font-family: 'meiryo';
}
.dn {
display: none !important;
}
article {
box-sizing: border-box;
padding: 10px;
width: 100%;
padding: 10px;
height: 400px;
background: #FFFFFF;
}
.title {
border-bottom: solid 1px #CCC;
}
.title,
.modalTitle,
.modalList {
margin: 0;
}
.btn {
cursor: pointer;
width: 100%;
border-radius: 3px;
background: #DDD;
padding: 5px 0;
text-align: center;
}
.modal {
display: none;
opacity: 0;
position: fixed;
width: 100%;
min-height: 100%;
top: 0;
left: 0;
background-color: #F5F5F5;
}
.modal.isOpen {
display: block;
opacity: 1;
animation: scaleUp 0.3s linear;
-webkit-animation: scaleUp 0.3s linear;
}
.modal.isStatic {
position: static;
}
.modal.isClose {
display: block;
animation: scaleDown 0.3s linear;
-webkit-animation: scaleDown 0.3s linear;
}
@keyframes scaleUp {
0% {
opacity: 0;
transform: scale(0, 0);
}
100% {
opacity: 1;
transform: scale(1, 1);
}
}
@-webkit-keyframes scaleUp {
0% {
opacity: 0;
transform: scale(0, 0);
}
100% {
opacity: 1;
transform: scale(1, 1);
}
}
@keyframes scaleDown {
0% {
opacity: 1;
transform: scale(1, 1);
}
100% {
opacity: 0;
transform: scale(0, 0);
}
}
@-webkit-keyframes scaleDown {
0% {
opacity: 1;
transform: scale(1, 1);
}
100% {
opacity: 0;
transform: scale(0, 0);
}
}
.modalHeader {
position: relative;
}
.modalTitle {
font-size: 15px;
padding: 5px;
background-color: #3F51B5;
color: #FFFFFF;
}
.btnClose {
position: absolute;
cursor: pointer;
top: 0;
right: 0;
text-align: center;
width: 33px;
height: 100%;
color: #FFF;
}
.btnClose:before,
.btnClose:after {
position: absolute;
display: block;
margin-top: -6px;
top: 50%;
content: "";
width: 10px;
height: 10px;
border-color: #FFF;
border-style: solid;
}
.btnClose:before {
left: 4px;
border-width: 2px 2px 0 0;
transform: rotate(45deg);
}
.btnClose:after {
right: 3px;
border-width: 2px 0 0 2px;
transform: rotate(-45deg);
}
.modalList {
padding: 10px;
list-style: none;
}
.modalList > li {
padding: 5px 10px;
border-radius: 3px;
border: solid 1px #ddd;
background-color: #FFF;
}
.modalList > li + li {
margin-top: 10px;
}
var SAMPLE = SAMPLE || {};
SAMPLE.FullScreenModal = function() {
this.$trigger = $('.jscModalTrigger');
this.$modal = $('.jscModal');
this.$contents = $('.jscContents');
this.init();
};
SAMPLE.FullScreenModal.prototype = {
HIDE_CLASS: 'dn',
OPEN_CLASS: 'isOpen',
STATIC_CLASS: 'isStatic',
CLOSE_CLASS: 'isClose',
init: function() {
this.$window = $(window);
this.$btnClose = this.$modal.find('.jscBtnClose');
this.toggleClassName = [this.OPEN_CLASS, this.CLOSE_CLASS, this.STATIC_CLASS].join(' ');
this.bindEvent();
},
bindEvent: function() {
var _self = this;
this.$trigger.on('click', function(e) {
e.preventDefault();
_self.openModal();
});
this.$modal.on('animationend webkitAnimationEnd', function() {
_self.toggleModalStatus();
});
this.$btnClose.on('click', function(e) {
e.preventDefault();
_self.closeModal();
});
},
openModal: function() {
this.windowScrollTop = this.$window.scrollTop();
this.$modal.addClass(this.OPEN_CLASS);
},
closeModal: function() {
this.$contents.removeClass(this.HIDE_CLASS);
this.$modal.toggleClass(this.toggleClassName);
this.$window.scrollTop(this.windowScrollTop);
},
toggleModalStatus: function() {
var isOpen = this.$modal.hasClass(this.OPEN_CLASS);
if (isOpen) {
this.$contents.addClass(this.HIDE_CLASS);
this.$modal.addClass(this.STATIC_CLASS);
this.$window.scrollTop(0);
return;
}
this.$modal.removeClass(this.CLOSE_CLASS);
},
};
new SAMPLE.FullScreenModal();
CSS
基本設定
- ベースとなる
.modal
にアニメーション用の.isOpen
と.isClose
を設定。 - モーダルは初期状態では非表示なので、ベースに
display: none;
を設定。 - アニメーション用クラスには
display: block;
を設定した上で、アニメーション用のプロパティを割り当てる。 - 開いた時には、ベースの非表示設定を上書きする必要があるため、
opacity: 1
を設定している。position については、開いた後 staticにしたかったので、設定。
.modal {
display: none;
opacity: 0;
position: fixed;
width: 100%;
min-height: 100%;
top: 0;
left: 0;
background-color: #F5F5F5;
}
.modal.isOpen {
display: block;
opacity: 1;
animation: scaleUp 0.3s linear;
-webkit-animation: scaleUp 0.3s linear;
}
.modal.isStatic {
position: static;
}
.modal.isClose {
display: block;
animation: scaleDown 0.3s linear;
-webkit-animation: scaleDown 0.3s linear;
}
keyframe
- アニメーションで変化させたい部分だけ書く。
- 開く時 → scaleUp、閉じる時→scaleDown として作成。
- それぞれ、opacityとscaleの変化を設定。
@keyframes scaleUp {
0% {
opacity: 0;
transform: scale(0, 0);
}
100% {
opacity: 1;
transform: scale(1, 1);
}
}
@-webkit-keyframes scaleUp {
0% {
opacity: 0;
transform: scale(0, 0);
}
100% {
opacity: 1;
transform: scale(1, 1);
}
}
@keyframes scaleDown {
0% {
opacity: 1;
transform: scale(1, 1);
}
100% {
opacity: 0;
transform: scale(0, 0);
}
}
@-webkit-keyframes scaleDown {
0% {
opacity: 1;
transform: scale(1, 1);
}
100% {
opacity: 0;
transform: scale(0, 0);
}
}
JSでの処理
開く時
- スクロール位置の保存
- モーダルに開くアニメーション用のクラスを付ける。
開くアニメーション終了時
- コンテンツ部分の状態を非表示(display: none)にする。
- モーダルのpositionをstaticにする。
- スクロール位置を最上部に持ってくる。
閉じる時
- コンテンツ部分の非表示用クラスを外す。
- モーダルの開くアニメーション用クラス、staticクラスを外しつつ、同時に閉じるアニメーション用クラスを付ける。
- スクロール位置をモーダルを開く前の位置に戻す。
閉じるアニメーション終了時
- 閉じるアニメーション用クラスを外す(と同時にCSSの設定上非表示になる)
Transitionを利用するパターンとの比較
- 「アニメーション終了時にstaticにする」という部分をCSSで設定できる。→ できるが、チラツキが気になったのでJSで処理するように修正。
-
display: none;
⇔display: block;
の変化を伴うコンテンツをアニメーションさせる際、JSでのクラスの付け外し時にsetTimeout()
等でタイミングをずらす必要がなくなる。 - タイミングをずらす必要が無いので、アニメーションさせるコンテンツに対して、
display: none;
用のクラスだけの付け外しがない。 - 閉じる時と開く時に別のアニメーションを設定する必要がある。
- アニメーション終了時に処理を行う場合は、
animationend
イベントを利用する。 - JSの処理がクラスの付け外し(+スクロール位置補正)と、特定クラスの有無による分岐処理だけになり、よりシンプルになった。