【Vue.js】Vue.jsがトランジション(アニメーション)を提供してる理由

## はじめに
仕事で使う事になったので1からVue.jsについて学んだ。ちゃんと覚えておかないとまずそうな事を備忘録として1つ1つ残しておく。

## Vue.jsのトランジション(アニメーション)
### なぜVue.jsがトランジションを提供しているか？
単純なトランジションであれば以下のようなソースコード（CSSトランジション）で実現できる。
![ezgif.com-gif-maker (18).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/5dbbc4f9-4842-5116-101f-21d336075fd5.gif)

動画のソースコードのCSSは以下（ソース全体は[ここを参照](https://github.com/yuta-katayama-23/intro-vuecli/commit/89a1944e6620479228fd40333356d0c651f4902e#r52555116)）。

```css:css-transition.css
div {
  width: 200px;
  transition: width 3s;
}
div {
  width: 400px;
}
```

が、`v-if`でfalseになった時に要素をゆっくりフェードアウトさせたいとなった場合には、様々な事を考えなければならない。
具体的には、以下のように`div`要素がありそのv-ifディレクティブの値が`true` -> `false`になった時に、どのようにCSSを適用させるのか？という問題がある。

| before | after |
|---|---|
| `<div v-if="true"></div>`  | `<div v-if="false"></div>`  |
| `div {`<br>`　opacity: 1;`<br>`　transition: opacity 5s;`<br>`}`  | `? {`<br>`　opacity: 0;`<br>`}`  |

この問題の解決策としては、JavaScriptで動的にStyleを変更する(クラス属性を付与したり削除したりする)という方法しかない。
が、それを自分でやろうとすると以下の手順を踏む必要があり複雑な実装が必要になる。そのためVue.jsがトランジションの機能`<transition></transition>`・`<transition-group></transition-group>`を提供してくれている。

1. v-ifがfalseになったのを検出
1. `opacity: 1;`を`opacity: 0;`に変える
1. 5秒間かけて変える＋5秒間はDOMから要素が消えないようにする（アニメーションにするために）
1. DOMから要素を削除する

### Vue.jsのトランジション
以下の動画のように、v-ifで定義している項目がアニメーションで消えるような実装が簡単にできる。
![ezgif.com-gif-maker (19).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/90819422-6b82-878c-d611-9afc15b88d3b.gif)

動画のソースコードは以下。

```vue:App.vue
<template>
  <div class="main">
    <button type="button" class="btn btn-primary" @click="show = !show">
      切り替え
    </button>
    <transition name="fade">
      <p v-if="show">hello</p>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: true,
    };
  },
};
</script>

<style scoped>
.fade-enter {
  /* 現れる時の最初の状態をここに定義する */
  opacity: 0;
}
.fade-enter-active {
  /* 現れる時（現れている最中）のトランジション・アニメーションの状態をここに定義する 
     何秒間かけてどのようなアニメーションを付けたいのか？  */
  transition: opacity 5s;
}
.fade-enter-to {
  /* 現れる時の最後の状態をここに定義する */
  opacity: 1; /* ← 本来これはデフォルトなので宣言する必要はないが今回はあえて */
}
.fade-leave {
  /* 消える時の最初の状態をここに定義する */
  opacity: 1; /* ← 本来これはデフォルトなので宣言する必要はないが今回はあえて */
}
.fade-leave-active {
  /* 消える時（消えている最中）のトランジション・アニメーションの状態をここに定義する 
     何秒間かけてどのようなアニメーションを付けたいのか？  */
  transition: opacity 5s;
}
.fade-leave-to {
  /* 消える時の最後の状態をここに定義する */
  opacity: 0;
}
/* 省略 */
</style>
```

`<style></style>`に書いてあるコメントの通りだが、トランジションのライフサイクルのようなものの各Stepに対応するクラスに対するCSSを定義し、そこにどういうトランジションで動いてほしいか？を定義していく事で、Vue.jsのトランジションが使える。

実際にどういう仕組みでVue.jsがトランジションを実現しているかについてだが、Vue.jsが、暗黙的に`<transition>`のname属性に`-enter`,`-enter-active`,`-enter-to`,`-leave`,`-leave-active`,`-leave-to`という接尾辞を付けたの6つのクラスを定義し、そのクラス属性をDOMに追加する事でアニメーションが機能するようにしている。
実際に動画のアニメーションは、以下の順番でクラス属性がVue.jsにより自動的に追加・削除される事で実現されている。

1. fade-leave, fade-leave-activeを追加
1. ブラウザのアニメーションフレームの1フレームの時間(1/60秒)が経過後、fade-leaveを削除し、fade-leave-active, fade-leave-toを追加
1. Vue.jsがDOMイベントのアニメーションイベントをキャッチし、アニメーションが完了した後、fade-leave-active, fade-leave-toを削除
1. fade-enter, fade-enter-activeを追加
1. ブラウザのアニメーションフレームの1フレームの時間(1/60秒)が経過後、fade-enterを削除し、fade-enter-active, fade-enter-toを追加
1. Vue.jsがDOMイベントのアニメーションイベントをキャッチし、アニメーションが完了した後、fade-enter-active, fade-enter-toを削除

※fade-enter-active, fade-leave-activeは全ての状態・Stepでクラス属性に追加されているので、直書きしたくなるが同じアニメーションを使いまわす時に不便なのでそれはしないようにする。

※ちなみに、トランジションのname属性がない場合、デフォルトではname="v"になるので、CSSは`v-…`のようになる

ソースコード全体は以下。

https://github.com/yuta-katayama-23/intro-vuecli/commit/aa89af984dcd8dc4416d71967381910724dd72a1

### Vue.jsのアニメーション
使い方は[]()と全く同じだが、アニメーションの場合`@keyframes`の`from, to`で最初・最後の状態を定義しているので、`-enter`, `-enter-to`, `-leave`, `-leave-to`は不要になる。
![ezgif.com-gif-maker (20).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/95ccfddc-e730-acb0-b5c4-e53e1f5c942b.gif)

動画のソースコードは以下。

```vue:App.vue
<template>
  <div class="main">
    <button type="button" class="btn btn-primary" @click="show = !show">
      切り替え
    </button>
    <transition name="fade">
      <p v-if="show">hello</p>
    </transition>
    <transition name="slide">
      <p v-if="show">bye</p>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: true,
    };
  },
};
</script>

<style scoped>
/* 省略 */

/* enter, enter-to, leave, leave-toは、keyframesに指定しているので定義する必要がない（Vue.jsが勝手にやってくれる） */
/* .slide-enter {
} */
.slide-enter-active {
  animation: slide-in 0.5s;
}
/* .slide-enter-to {
}
.slide-leave {
} */
.slide-leave-active {
  animation: slide-in 0.5s reverse;
}
/* .slide-leave-to {
} */

@keyframes slide-in {
  from {
    transform: translateX(100px);
  }
  to {
    transform: translateX(0);
  }
}

/* 省略 */
</style>
```

ソースコード全体は以下。

https://github.com/yuta-katayama-23/intro-vuecli/commit/762e10639dfc25a074a31869407a30e8fb4afea6

### Vue.jsのトランジションが使えるパターン3つ
トランジションが使えるパターンとしては以下の3つだけ。

#### ①要素が消えたり現れたりする（Aが表示される・されないの切り替わりのトランジション・アニメーション）
※transitionは1つの要素しかそのタグ内に存在してはいけないので注意

```vue:TransitionOne.vue
<transition>
   <div v-if="show">消えたり現れたり</div>
</transition>
```

#### ②複数の要素がありそれが入れ替わるようなものだが、最終的には1つの要素のみになる（AかBかの切り替えを行う際のトランジション・アニメーション）
```vue:TransitionOne.vue
<transition>
   <div v-if="show">こっちか</div>
   <div v-else>こっちになる</div>
</transition>
```

※上記の1つのみのルールがあるため、`v-show`は使えない

#### ③リストレンダリングでリストが追加されたり削除されたりする（リストの項目が増える時の、逆に消える時のトランジション・アニメーション）
```vue:TransitionOne.vue
<transition-group>
   <li v-for="item in items" :key="item">{{ item }}</li>
</transition-group>
```

## Vue.jsの勉強メモ一覧記事へのリンク
Vue.jsについて勉強した際に書いた勉強メモ記事のリンクを集約した記事。

- https://qiita.com/yuta-katayama-23/items/dabefb59d16a83f1a1d4