全画面モーダル（CSS Animation）

# 概要
- スマートフォン向け。
- ボタンを押すと画面いっぱいにモーダルが表示される。
- モーダル右上の「×」ボタンで閉じる。
- 表示と非表示をCSS Animationで演出。

# CSS Animation
[CSS Animation - Can I use](http://caniuse.com/#feat=css-animation)

- スマートフォンだと、`-webkit-`のプレフィックス使えば全てカバーできる状況と言っていい（Android 4以上）。
- IE9が対象外で良いなら、PCでも使える。

# サンプルコード
http://codepen.io/yama-t/full/BKBpMm/

```html
<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>
```

```css
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;
}
```

```js
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にしたかったので、設定。

```css
.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の変化を設定。

```css
@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の処理がクラスの付け外し（＋スクロール位置補正）と、特定クラスの有無による分岐処理だけになり、よりシンプルになった。

# 参考サイト
- [animationend Event](http://www.w3schools.com/jsref/event_animationend.asp)