Vue.jsで"≡"を作る

スマホナビゲーションの定番、ハンバーガーメニューを
`Vue.js`で作ってみたメモ。
![hamburger.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216132/9160b702-1546-0cc9-087a-9f2b2edbea5e.gif)

`Vue.js`で作る時は、`template`の中に3つイベントを書きます。
クリックした時、マウスが乗った時、マウスが離れた時です。

状態を`data属性`に保存しておかないといけないので、
`data-state`と`data-mouse`を用意しています。

`state`はメニューを開いているか、閉じているか？を記録します。
`mouse`は、マウスが乗ったか、離れたか？を記録します。

```App.vue
<template>
  <div id="app">
    <header class="header">
      <div
        class="menu"
        data-state="hoge"
        data-mouse="fuga"
        @click="click"
        @mouseenter="enter"
        @mouseleave="leave"
        >
        <div class="line line-1"></div>
        <div class="line line-2"></div>
        <div class="line line-3"></div>
        <div class="line-close line-close-1"></div>
        <div class="line-close line-close-2"></div>
      </div>
    </header>
  </div>
</template>
```
次に、`Javascript`を書きます。クリックしたら`state`に`batsu`を入れます。
もし`state`が`batsu`だったら`hamburger`を入れます。

マウスが乗ったら`mouse`に`enter`、離れたら`leave`を入れます。

```App.vue.js
<script>
export default {
  name: 'App',
  methods:{
    click(e){
      if(e.target.dataset.state !== 'batsu'){
        e.target.dataset.state = 'batsu';
      }else{
        e.target.dataset.state = 'hamburger';
      }
    },
    enter(e){
      e.target.dataset.mouse = 'enter';
    },
    leave(e){
      e.target.dataset.mouse = 'leave';
    },
  }
}
</script>
```
コレでイベントの制御は完了です。書き方さえ覚えてしまえば`jQuery`並に…
いや、`$('').toggleClass()`の方がカンタンですけどね。。
DOM操作に関しては`jQuery`が神すぎるって事で。。

##スタイルを作っていく。
動きがあるので、CSSが面倒ですよね。いくつかに分割して書きます。
まずは基本セットから。

```App.vue.scss
.menu {
	position: absolute;
	top: 18px;
	right: 18px;
	width: 44px;
	height: 40px;
	cursor: pointer;
	touch-action: manipulation;

  .line {
    position: absolute;
    left: 12px;
    width: 20px;
    height: 3px;
    overflow: hidden;
    pointer-events: none;

    &.line-1 { top: 12px }
    &.line-2 { top: 19px }
    &.line-3 { top: 26px }
    &::before,
    &::after {
      content: "";
      display: block;
      position: absolute;
      top: 0;
      left: 0;
      width: 20px;
      height: 3px;
      background-color: #222;
      transition-property: transform;
      transition-duration: .15s;
      transition-timing-function: cubic-bezier(0.5, 0.1, 0.1, 1)
    }
    &::after {
      transform: translate3d(-100%, 0, 0);
    }
  }
}
```
ちょっと見慣れない奴は解説します。
まず`touch-action: manipulation;`ですが、
これは標準的なタップ以外を無視するヤツです。ダブルタップとか（？）

次に`transition-timing-function: cubic-bezier(0.5, 0.1, 0.1, 1)`
ですが、これはアニメーションのイージングをどうするかってヤツです。
「ベジェ曲線」の「アンカーポイント」の位置を示しています。

`transform: translate3d(-100%, 0, 0);`は、
これは昔`text-indent:-100%`っていうCSSの使い方があったと思うんですが、
今はこの書き方をしてる、って感じです。左にしまっておく感じです。

##アニメーション
data属性に`enter`が入った時のアニメーションです。
最初に表示されているハンバーガーは疑似要素の`before`ですね。
それを右に100%（消えるまで）送ってしまい、
次に疑似要素`after`を左から`before`が元々あった位置に持ってきます。

```App.vue.scss
.menu[data-mouse="enter"] .line::before {
	transform: translate3d(100%, 0, 0);
}

.menu[data-mouse="enter"] .line::after {
	transform: translate3d(0, 0, 0);
}
```
このままだと「≡が右に消えて、左から出てきましたね。」
というだけなんですが、3本の線それぞれにdelayを付けてあげることで
なんか、「斜めに消えてる」っていうより、「キラーンと光ってる」みたいな感じに見えます。
（アニGIFだと表現できてないですが、実際はマウスが乗った時にも動きます。）

コレ最初に考えた人天才ですね。。

```App.vue.scss
.menu[data-mouse="enter"] .line-1::before{
	transition-delay: 0s
}

.menu[data-mouse="enter"] .line-1::after{
	transition-delay: 0.15s;
}

.menu[data-mouse="enter"] .line-2::before{
	transition-delay: 0.05s
}

.menu[data-mouse="enter"] .line-2::after{
	transition-delay: 0.2s;
}

.menu[data-mouse="enter"] .line-3::before{
	transition-delay: 0.1s;
}

.menu[data-mouse="enter"] .line-3::after{
	transition-delay: 0.25s;
}
```
ちなみに「×印」になる場合、この3本は右に消えます。

```App.vue.scss
.menu[data-state="batsu"] .line::before,
.menu[data-state="batsu"] .line::after {
	transform: translate3d(100%, 0, 0)
}
```

##バツ印を作る。
つぎはバツ印です。バツ印は`state`に`batsu`が入った時のアニメーションです。
基本セットはこの2本です。

```App.vue.scss
.menu .line-close {
	position: absolute;
	top: 50%;
	left: 50%;
	width: 22px;
	height: 2px;
	background-color: #111;
	overflow: hidden;
	transition-property: opacity, transform;
	transition-duration: .4s;
	transition-timing-function: cubic-bezier(0.5, 0.1, 0.1, 1);
	pointer-events: none
}

.menu .line-close-1 {
	opacity: 0;
	transform: translate(-50%, -50%) rotate(-80deg)
}

.menu .line-close-2 {
	opacity: 0;
	transform: translate(-50%, -50%) rotate(-80deg)
}
```
透明だから見えないんですけど、こんな感じで疑似要素が2本重なっています。
それを`−50％`して真ん中に持ってきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216132/94337b7c-b112-5b3c-63d0-384cb8d7fd63.png)
　↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216132/e8c1795c-af8f-23d2-c9f2-65022e815a77.png)


さらに80度回転した状態にして透明にしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216132/9d78cfb5-6f40-8760-c9df-5ce9cf8c82eb.png)

それを表示する時は角度を「45度」、「−45度」にアニメーションします。

```App.vue.scss
.menu[data-state="batsu"] .line-close-1 {
	opacity: 1;
	transform: translate(-50%, -50%) rotate(45deg);
	transition-delay: 0.3s
}

.menu[data-state="batsu"] .line-close-2 {
	opacity: 1;
	transform: translate(-50%, -50%) rotate(-45deg);
	transition-delay: 0.3s
}
```
そうすると、クルリンと折りたたみ椅子が開くようなバツマークになります。

![hamburger.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216132/d6582b33-7889-4d6c-726b-c6bebb4ba506.gif)

上から下までつなげたCSSです。

```App.vue.scss
<style lang="scss" scoped>
.menu {
	position: absolute;
	top: 18px;
	right: 18px;
	width: 44px;
	height: 40px;
	cursor: pointer;
	touch-action: manipulation;

  .line {
    position: absolute;
    left: 12px;
    width: 20px;
    height: 3px;
    overflow: hidden;
    pointer-events: none;

    &.line-1 { top: 12px }
    &.line-2 { top: 19px }
    &.line-3 { top: 26px }
    &::before,
    &::after {
      content: "";
      display: block;
      position: absolute;
      top: 0;
      left: 0;
      width: 20px;
      height: 3px;
      background-color: #222;
      transition-property: transform;
      transition-duration: .15s;
      transition-timing-function: cubic-bezier(0.5, 0.1, 0.1, 1)
    }
    &::after {
      transform: translate3d(-100%, 0, 0);
    }
  }
}

.menu[data-mouse="enter"] .line::before {
	transform: translate3d(100%, 0, 0);
}

.menu[data-mouse="enter"] .line::after {
	transform: translate3d(0, 0, 0);
}

.menu[data-state="batsu"] .line::before,
.menu[data-state="batsu"] .line::after {
	transform: translate3d(100%, 0, 0)
}

.menu .line-close {
	position: absolute;
	top: 50%;
	left: 50%;
	width: 22px;
	height: 2px;
	background-color: #111;
	overflow: hidden;
	transition-property: opacity, transform;
	transition-duration: .4s;
	transition-timing-function: cubic-bezier(0.5, 0.1, 0.1, 1);
	pointer-events: none
}

.menu .line-close-1 {
	opacity: 0;
	transform: translate(-50%, -50%) rotate(-80deg)
}

.menu .line-close-2 {
	opacity: 0;
	transform: translate(-50%, -50%) rotate(-80deg)
}

.menu[data-state="batsu"] .line-close-1 {
	opacity: 1;
	transform: translate(-50%, -50%) rotate(45deg);
	transition-delay: 0.3s
}

.menu[data-state="batsu"] .line-close-2 {
	opacity: 1;
	transform: translate(-50%, -50%) rotate(-45deg);
	transition-delay: 0.3s
}

.menu[data-mouse="enter"] .line-1::before,
.menu[data-state="batsu"] .line-1::before {
	transition-delay: 0s
}

.menu[data-mouse="enter"] .line-1::after,
.menu[data-state="batsu"] .line-1::after {
	transition-delay: 0.15s;
}

.menu[data-mouse="enter"] .line-2::before,
.menu[data-state="batsu"] .line-2::before {
	transition-delay: 0.05s
}

.menu[data-mouse="enter"] .line-2::after,
.menu[data-state="batsu"] .line-2::after {
	transition-delay: 0.2s;
}

.menu[data-mouse="enter"] .line-3::before,
.menu[data-state="batsu"] .line-3::before {
	transition-delay: 0.1s;
}

.menu[data-mouse="enter"] .line-3::after,
.menu[data-state="batsu"] .line-3::after {
	transition-delay: 0.25s;
}

.menu[data-state="hamburger"] .line::before,
.menu[data-state="hamburger"] .line::after {
	transform: translate3d(0, 0, 0)
}

.menu[data-state="hamburger"] .line-1::before,
.menu[data-state="hamburger"] .line-1::after {
	transition-delay: 0.3s
}

.menu[data-state="hamburger"] .line-2::before,
.menu[data-state="hamburger"] .line-2::after {
	transition-delay: 0.35s
}

.menu[data-state="hamburger"] .line-3::before,
.menu[data-state="hamburger"] .line-3::after {
	transition-delay: 0.4s
}
</style>
```

さわってて面白いアイコンだなと。。以上です。


##参考資料
https://jp.vuejs.org/v2/guide/transitions.html
https://jp.vuejs.org/v2/guide/events.html
http://www.htmq.com/css3/transition-timing-function.shtml
https://developer.mozilla.org/ja/docs/Web/CSS/transform-function/translate3d