Vue.jsミニハンズオン（TODOリストにアニメーションをつける）

## Vue.jsミニハンズオン（TODOリストにアニメーションをつける）

AngularでもReactでもriot.jsでも満足できなかったひとに、ぴったりフィットなJSフレームワーク「Vue.js」のざっくりハンズオン第２回目です。

第１回目は「[Vue.jsミニハンズオン（TODOリスト作成）](https://qiita.com/moonglows76/items/358ef3cd1566c38ece3a)」をご覧ください。

このハンズオンではnode.jsのパッケージは使わず、Google ChromeとテキストエディタがあればOKです。

Vue.jsミニハンズオンのシリーズは以下を公開しています。

1. [Vue.jsミニハンズオン（TODOリスト作成）](https://qiita.com/moonglows76/items/358ef3cd1566c38ece3a)
1. [Vue.jsミニハンズオン（TODOリストにアニメーションをつける）](https://qiita.com/moonglows76/items/d550f0243319d7111ee3)
1. [Vue.jsミニハンズオン（TODOリストをコンポーネント化する）](https://qiita.com/moonglows76/items/242aaf265672797c6800)

## 今回の目標

目標は第１回目で作ったシンプルなTODOリストにアニメーションをつけることです。

## まずはシンプルなアニメーションを作ってみる

TODOリストにアニメーションをつける方法がまだわからないので、
ひとまずアニメーションの付け方を学んでいきます。

ボタンで`p`要素を表示/非表示してみます。
必要な知識は下記。

* Vue.jsが持っているカスタム要素、`transition`要素を使う
* `transition`要素の`name`属性でアニメーション用CSSの接頭辞を指定する
* 表示/非表示を管理するデータを用意する（ここでは`show`）
* `p`要素に`v-if`属性と表示/非表示を管理するデータを関連付ける

```html:animation.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>まずはシンプルなアニメーションを作ってみる</title>
  <style>
.fade-enter-active, .fade-leave-active {
  transition: opacity .5s
}
.fade-enter, .fade-leave-to {
  opacity: 0
}
  </style>
</head>
<body>

<div id="demo">
  <button v-on:click="show = !show">
    Toggle
  </button>
  <transition name="fade">
    <p v-if="show">hello</p>
  </transition>
</div>

<script src="https://unpkg.com/vue"></script>
<script src="./app_animation.js"></script>

</body>
</html>
```

```JavaScript:app_animation.js
const vm = new Vue({
  el: '#demo',
  data: {
    show: true
  }
})
```

[CODEPEN](https://codepen.io/moonglows76/pen/qVWxGP)

ボタンをクリックすると`show`のtrue, falseという値がひっくり返るため、
p要素は`v-if`ディレクティブによって表示/非表示が切り替わります。
`v-if`ディレクティブは表示/非表示するというより、要素を追加/削除するものなんですが、
`transition`要素をつかうことでアニメーションを付加できます。


## アニメーションはCSSで設定する（JavaScriptでもできるけども）

上の`style`要素内には
`.fade-enter-active, .fade-leave-active`
や
`.fade-enter, .fade-leave-to`
という、これはいつ使うのかな？と思うようなクラス名がありました。

これはアニメーション時にVue.jsが要素に付加する特定のクラス名です。
`transition`要素の`name`属性値（上のhtmlでは`fade`）を接頭辞としてクラス名を作り出します。

- fade-enter
- fade-enter-to
- fade-enter-active
- fade-leave
- fade-leave-to
- fade-leave-active

これらのクラス名を使ってCSSでアニメーションを指定することで
自由なアニメーションの設定ができます。

Vue.js公式サイトに掲載されているの[アニメーションの流れの図](https://jp.vuejs.org/v2/guide/transitions.html#トランジションクラス)を見てみてください。
Enterは非表示→表示、Leaveは表示→非表示のときを表しています。

`.fade-enter-active, .fade-leave-active`のように`-active`とついているクラスは
アニメーションが動作する間ずっと要素に付加されるクラスです。
これらには`transition`,`animation`プロパティを指定することになります。

```
.fade-enter-active, .fade-leave-active {
  transition: opacity .5s
}
```

そして`.fade-enter, .fade-leave-to`のように上の図の左端と右端にあたる「非表示の状態」の見た目を`opacity: 0;`などで指定します。

```
.fade-enter, .fade-leave-to {
  opacity: 0
}
```

## animationプロパティを使ってみる

上記の例では`transition`プロパティで比較的単純なアニメーションを表示しました。
`animation`プロパティを使うともう少し複雑なアニメーションが表示できます。

animation.htmlにの`body`要素内に下記を追加します。

```html:animation.html
<div id="example-2">
  <button @click="show = !show">Toggle show</button>
  <transition name="bounce">
    <p v-if="show">Look at me!</p>
  </transition>
</div>
```

app_animation.jsに下記スクリプトを追加します。

```JavaScript:app_animation.js
new Vue({
  el: '#example-2',
  data: {
    show: true
  }
})
```

また、animation.htmlの`style`要素内に下記を追加します。

```html:animation.html
#example-2 p {
  background-color: pink;
}
.bounce-enter-active {
  animation: bounce-in .5s;
}
.bounce-leave-active {
  animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(1);
  }
}
```

[CODEPEN](https://codepen.io/moonglows76/pen/jaNzey)

追加したhtmlとjsはほぼ最初に入れたものと同じです。
`transition`要素name属性値だけ違います。

CSSは`-active`とついているクラスの両方に`animation`プロパティを設定し、
`@keyframes`ルールで`bounce-in`と名前をつけたアニメーションを呼び出しています。
注目するのは`bounce-leave-active`というクラスの`animation`プロパティには`reverse`という値がついてることです。これは`animation-direction`プロパティの値で、アニメーションの向きを逆にするという指定になります。

ここのアニメーションの指定では下記の用に変化します。

- Enter時：大きさ0倍→1.5倍→1倍
- Leave時：大きさ1倍→1.5倍→0倍

## animate.cssを使いたい

いろんな種類のアニメーションが設定されている[animate.css](https://daneden.github.io/animate.css/)を使いたい！
そんな要望にVue.jsは応えてくれます。

animation.htmlの`head`要素内に下記要素を追記します。

```html:animation.html
<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
```

animation.htmlの`body`要素内に下記要素追記します。

```html:animation.html
<div id="example-3">
  <button @click="show = !show">
    Toggle render
  </button>
  <transition
    name="custom-classes-transition"
    enter-active-class="animated tada"
    leave-active-class="animated bounceOutRight"
  >
    <p v-if="show">hello</p>
  </transition>
</div>
```

app_animation.jsに下記スクリプトを追加します。

```JavaScript:app_animation.js
new Vue({
  el: '#example-3',
  data: {
    show: true
  }
})
```

[CODEPEN](https://codepen.io/moonglows76/pen/MOgVMO)

`transition`要素の開始タグが重要なところです。

```
  <transition
    name="custom-classes-transition"
    enter-active-class="animated tada"
    leave-active-class="animated bounceOutRight"
  >
```

`name`属性値`custom-classes-transition`はanimate.cssのような名前があらかじめ決まっているクラスを使用するときに指定します。

- `enter-active-class`
- `leave-active-class`

といった属性は先に出した図のまさにその場所にあてはまるクラスを指定します。
ここではanimate.cssで固定で指定する`animated`というクラスとアニメーションの種類を指定する`tada`、'bounceOutRight'などのクラスを指定しています。

animate.cssのいろんなクラスに差し替えて試してみると面白いです。
基本的には`In`,`Out`がクラス名に入っているものを利用するのがスムーズに利用できるようです。

察しの良い方は気づくかもしれませんがほかの状態の属性も存在します。
なにかしら複雑な指定をするときには使えるかも。

- enter-class
- enter-active-class
- enter-to-class
- leave-class
- leave-active-class
- leave-to-class

## リストのアニメーション

これまでは１つの項目をアニメーションさせることを見てきましたがリストに項目を追加・削除するアニメーションはどのように実現できるでしょうか。

ここで初めて`transition-group`要素がでてきます。

animation.htmlに下記を追加します。
`name`属性値は`transition`要素と同じくクラス名の接頭辞になります。
`tag`属性値は`transition-group`が置き換わる要素タイプになります。何も指定しなければ`span`要素が表示されます。

`v-for`属性が入っている`span`要素は要素を一意に指定できるものとして
`key`を指定する必要があります。
ここはお忘れなく。


```html:animation.html
<div id="list-complete-demo" class="demo">
  <button v-on:click="add">Add</button>
  <button v-on:click="remove">Remove</button>
  <transition-group name="list-complete" tag="p">
    <span
      v-for="item in items"
      v-bind:key="item"
      class="list-complete-item"
    >
      {{ item }}
    </span>
  </transition-group>
</div>
```

app_animation.jsに下記を追加します。
これは単純に配列の操作をしているだけです。

```JavaScript:app_animation.js
new Vue({
  el: '#list-complete-demo',
  data: {
    items: [1,2,3,4,5,6,7,8,9],
    nextNum: 10
  },
  methods: {
    randomIndex: function () {
      return Math.floor(Math.random() * this.items.length)
    },
    add: function () {
      this.items.splice(this.randomIndex(), 0, this.nextNum++)
    },
    remove: function () {
      this.items.splice(this.randomIndex(), 1)
    },
  }
})
```

また、animation.htmlの`style`要素内に下記を追加します。

```html:animation.html
.list-complete-item {
  transition: all 1s;
  display: inline-block;
  margin-right: 10px;
}
.list-complete-enter, .list-complete-leave-to
/* .list-complete-leave-active for below version 2.1.8 */ {
  opacity: 0;
  transform: translateY(30px);
}
.list-complete-leave-active {
  position: absolute;
}
```

[CODEPEN](https://codepen.io/moonglows76/pen/Zazorq)

AddボタンとRemoveボタンをクリックすると要素がスルッと動いて追加・削除されます。
どうやらこのスムーズなアニメーションにはコツがあるようです。

- いままで`-active`に指定していた`transition`プロパティや`animation`プロパティを要素の通常状態に入れ込む

```
.list-complete-item {
  transition: all 1s; /* 重要 */
  display: inline-block;
  margin-right: 10px;
}
```

- `-leave-active`には`position: absolute;`を入れる

```
.list-complete-leave-active {
  position: absolute;
}
```

アニメーションする要素は`position: static;`のレイアウト状態からはずすことで
他の要素の動きをスムーズにするということのようです。
`transform`プロパティの`translate`関数で位置を動かしても実際の要素の場所は変わってませんもんね。


## TODOリストにアニメーションをつける

ようやくここまでやってきました。あとは前回のソースにいままで学んだことを入れ込むだけです。
なんとjsファイルを編集する必要はありません。htmlとcssだけの編集で済みます。

```todo.html
  <ul>
    <li v-for="item in items">
      <label v-bind:class="{ done: item.isChecked }">
        <input type="checkbox" v-model="item.isChecked" v-on:change="saveTodo"> {{ item.title }}
      </label>
    </li>
  </ul>
```

↓

```todo.html
  <transition-group name="list-complete" tag="ul">
    <li v-for="item in items" v-bind:key="item" class="list-complete-item">
      <label v-bind:class="{ done: item.isChecked }">
        <input type="checkbox" v-model="item.isChecked" v-on:change="saveTodo"> {{ item.title }}
      </label>
    </li>
  </transition-group>
```

css部分に下記を入れます。

```todo.html
.list-complete-item {
  transition: all 1s;
}
.list-complete-enter, .list-complete-leave-to
/* .list-complete-leave-active for below version 2.1.8 */ {
  opacity: 0;
  transform: translateX(30px);
}
.list-complete-leave-active {
  position: absolute;
}
```

[CODEPEN](https://codepen.io/moonglows76/pen/ZazREZ)


## Vue.js公式のアニメーションのガイドはかなりボリュームがあります

Vue.js公式のアニメーションのガイドはについては下記をご覧ください。
このハンズオンの例の大半はこのガイドから持ってきました。
そしてかなりはしょっています。しっかり学びたい方はこちらでも学んでみてください。
とても良くまとまっていて勉強しやすいですよ。

https://jp.vuejs.org/v2/guide/transitions.html