【Vue.js】トランジション

##Vue.jsが提供するトランジションパターン

表示・非表示系

```vue
<transition>
  <div v-if="show">消えたり現れたりする</div>
</transition>
```

入れ替わり系

```vue
<transition>
  <div v-if="show">こっちか</div>
  <div v-else>こっちになる</div>
</transition>
```

リスト表示系

```vue
<transition-group>
  <li v-for="item in items">{{ item }}</li>
</transition-group>
```

###下記のような、ボタンクリックで表示と非表示を切り替えるプログラムがあるとする。

![67bc9519c682c984ae5aa07ee4b107e9.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/95e15cf5-b469-4913-d819-d144f637ff39.gif)

```vue
<template>
  <div>
    <button @click="show = !show">change</button>
      <p v-if="show">hello</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: true,
    };
  },
};
</script>
```
###トランジションを用いて、下記のように5秒かけて表示、非表示が切り替わるようにする。
![34495990cbd4762c8c49ce5b830e6aa0.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/9cf7bb88-3ba6-c99b-0734-07116275cea5.gif)

##トランジションクラス分類

\<transition name="fade">の時

| トランジションクラス | 状態 |
|:--|:--|
|fade-enter| ・現れる時の最初の状態<br>・要素がtrueになる直前に、このクラスが適用され、trueになった直後に削除される。|
| fade-enter-active  |  ・現れる時のトランジションの状態<br>・要素が挿入される前に追加され、トランジション/アニメーションが終了すると削除さる。<br>・トランジションの継続時間や、変化させたいcssプロパティーを指定する。 |
|  fade-enter-to | ・現れる時の最後の状態<br>・要素が挿入された 1 フレーム後に追加され (同時に v-enter が削除される)、トランジション/アニメーションが終了すると削除される。 |
|  fade-leave |  ・消える時の最初の状態<br>・ 要素がfalseになる直前に、このクラスが適用され、falseになった直後に削除される。||
| fade-leave-active   | ・消える時のトランジションの状態<br>・要素が削除される前に追加され、トランジション/アニメーションが終了すると削除さる。<br>・トランジションの継続時間や、変化させたいcssプロパティーを指定する。 |
|  fade-leave-to  | ・消える時の最後の状態<br>・要素がfalseになった直後に、このクラスが適用されトランジション/アニメーションが終了すると削除される。 |

```vue
<template>
  <div>
    <button @click="show = !show">change</button>
    <transition name="fade">
      <p v-if="show">hello</p>
    </transition>
    <p></p>
  </div>
</template>

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

<style scoped>
.fade-enter {
  opacity: 0;
}
.fade-enter-active {
  transition: 5s;
}
.fade-enter-to {
  opacity: 1;
}
.fade-leave {
  opacity: 1;
}
.fade-leave-active {
  transition: 5s;
}
.fade-leave-to {
  opacity: 0;
}
</style>
```

transitionタグで囲んだ部分に、トランジションが適用される。
トランジションのクラス名は、nameに指定した値を接頭語とする。
例）
name="fade"の場合
fade-enter

また、この場合、非表示の際は、→薄くなってく→5秒かけて透明(opacity 0)になる（と同時にDOMが消去される）となっている。
よって、fade-leave-to　を opacity: 0.5;にした場合は下記のような挙動となる。
![7befbc28b36b50acf14f0ad56a7f3565.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/ac60630f-4264-009b-8d70-ae597fc3bc7b.gif)

##トランジションCSSアニメーションを適用する。

![41556810a0c33b22d653fdf6e5d065d6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/50cf3615-d66f-79ee-3e67-6acdec77bd9a.gif)

```vue
<template>
  <div>
    <button @click="show = !show">change</button>
    <transition name="slide">
      <p v-if="show">bye</p>
    </transition>
    <p></p>
  </div>
</template>

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

<style scoped>
.slide-enter-active {
  animation: slide-in 0.5s;
}

.slide-leave-active {
  animation: slide-in 0.5s reverse;
}

@keyframes slide-in {
  from {
    transform: translateX(100px);
  }
  to {
    transform: translateX(0px);
  }
}
</style>
```

cssアニメーションが、最初の状態と終わりの状態を定義しているため、enter-activeとleave-active以外は不要。

表示させる時 （つまり、v-ifがfalseからtrueになる時）は、slide-enter-activeクラスにより、5秒後に表示する。また、animeation属性にslide-inが適用されているので、x軸方向に100pxの位置から0pxの位置に移動する。

消去する時（つまり、v-ifがfalseからtrueになる時）は、slide-leave-activeクラスにより、5秒後に消去する。また、animeation属性にslide-inが適用されて、さらにreverse（逆転)が指定されているので、x軸方向に0pxの位置から1000pxの位置に移動する。

##cssトランジションとcssアニメーションを同時に適用させる。

```vue
<template>
  <div>
    <button @click="show = !show">change</button>
    <transition name="slide">
      <p v-if="show">bye</p>
    </transition>
    <p></p>
  </div>
</template>

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

<style scoped>
.slide-enter,
.slide-leave-to {
  opacity: 0;
}

.slide-enter-active {
  animation: slide-in 0.5s;
  transition: opacity 0.5s;
}

.slide-leave-active {
  animation: slide-in 0.5s reverse;
  transition: opacity 0.5s;
}

@keyframes slide-in {
  from {
    transform: translateX(100px);
  }
  to {
    transform: translateX(0px);
  }
}
</style>
```

デフォルトでopacity: 1;は設定されるため、
slide-leave
slide-enter-to
に対する設定は不要。

##複数の要素を切り替えるトランジション

下記のように、スライドして表示を切り替えるプログラムを目標とする。
![0dbbd43c8c81686aaecf21bedbe3c0fa.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/e3447ef3-836a-3d75-a1a8-7d1c70d13818.gif)

まずは、下記のように、v-elseを配置して切り替えを試みる。

```vue
<template>
  <div>
    <button @click="show = !show">change</button>
    <transition name="slide">
      <p v-if="show">さよなら</p>
      <p v-else>こんにちは</p>
    </transition>
  </div>
</template>

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

<style scoped>
.slide-enter-active {
  animation: slide-in 0.5s;
}

.slide-leave-active {
  animation: slide-in 0.5s reverse;
}

@keyframes slide-in {
  from {
    transform: translateX(100px);
  }
  to {
    transform: translateX(0px);
  }
}
</style>
```
![2bb3b09e6f9737739da0aef674c8fde0.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/38688983-dedf-8c64-5df1-dcd1d6e5606f.gif)

この場合、上記のようにアニメーションがかからない。
これは、なぜかというとvueは効率よく描写しようとするため、同じpタグの場合、DOMをそのまま使用し、中身（こんにちは←→さようなら）だけ変更する。そして、transitionはあくまでDOMが、出現、消去された時に適応されるので、アニメーションが当たらないということが起こる。
これを、回避するために、key属性を与えて、それぞれのDOMを別々の物と認識させる。

```vue
      <p v-if="show" key="bye">さよなら</p>
      <p v-else key="hello">こんにちは</p>
```
![0f32e85cfc45bcef87891ebfc1969440.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/246b77d3-0379-4625-fb83-03869ae10a85.gif)
上記のように、それぞれのpタグが競合してしまう。

そこで、トランジションモードを設定する。

|  | 内容 |
|:-:|:--|
|  in-out | 最初に新しい要素がトランジションして、それが完了したら、現在の要素がトランジションアウトする。  |
| out-in  |  最初に現在の要素がトランジションアウトして、それが完了したら、新しい要素がトランジションインする。 |

下記のように out-in トランジションを設定する。

```vue
    <transition name="slide" mode="out-in">
      <p v-if="show" key="bye">さよなら</p>
      <p v-else key="hello">こんにちは</p>
    </transition>
```
これで、目的通りの挙動となる。

##トランジショングループを使ってリストトランジションを作る

トランジショングループとトランジションの違い

1.複数要素をとることができる。
トランジションは単一要素しかとることができないが、トランジショングループは複数要素をとることができる。

```vue
<transition>
 <div></div>
 <div></div>
</transition>
```
上記は不可。

2.複数要素には必ずkey属性をつける必要がある。

```vue
<transition-group>
  <li v-for="number in numbers" :key="number" @click="remove(index)">
    {{ number }}
  </li>
</transition-group>
```

3.transition-group は　spanタグとなる。
transitionなタグを形成しないが、transition-group は デフォルトでspanタグとなる。

```vue
<transition-group>
  <li v-for="number in numbers" :key="number" @click="remove(index)">
    {{ number }}
  </li>
</transition-group>
```
↓ 生成されるhtml

```vue
<span data-v-7ba5bd90="">
  <li data-v-7ba5bd90=""> 8 </li>
  <li data-v-7ba5bd90=""> 10 </li>
  <li data-v-7ba5bd90=""> 5 </li>
  <li data-v-7ba5bd90=""> 11 </li>
  <li data-v-7ba5bd90=""> 2 </li>
</span>
```

なお、span以外のタグを指定したい場合は、tag="div"のような属性を設定することにより可能となる。

4.transition-group は　mode属性がない。
 置き換えるという概念がないため、mode属性はない（必要ない）

5.transition-groupには、７つ目のクラス、v-moveが存在する。

下記のような、追加ボタンを押すとランダムな数字がランダムな位置に追加され、また、数字をクリックすると削除されるプログラムを作るとする。
追加削除の際にフェイドアニメーションにより滑らかな挙動を行う。
![16527ad7329b116145681db95401ed36.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/1aaeaa63-1332-b439-3dac-5f73716be5d3.gif)

```vue
<template>
  <div>
    <button @click="add">追加</button>

    <ul>
      <transition-group name="fade">
        <li
          style="cursor: pointer"
          v-for="(number, index) in numbers"
          :key="number"
          @click="remove(index)"
        >
          {{ number }}
        </li>
      </transition-group>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      numbers: [0, 1, 2],
      nextNumber: 3,
      show: true,
    };
  },
  methods: {
    randomIndex() {
      return Math.floor(Math.random() * this.numbers.length);
    },
    add() {
      this.numbers.splice(this.randomIndex(), 0, this.nextNumber);
      this.nextNumber += 1;
    },
    remove(index) {
      this.numbers.splice(index, 1);
    },
  },
};
</script>

<style scoped>
.fade-enter {
  opacity: 0;
}
.fade-enter-active {
  transition: opacity 3s;
}
.fade-enter-to {
  opacity: 1;
}
.fade-leave {
  opacity: 1;
}
.fade-leave-active {
  transition: opacity 3s;
}
.fade-leave-to {
  opacity: 0;
}
</style>
```
![84c9e93ecb8a03f818b0d97cfec1d9b1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/4fc5c053-c4ae-0e6c-9cc2-802925f605cc.gif)

上記のように、滑らかに追加されない。
これは、追加ボタンを押した場合、DOMが挿入されてからアニメーションが開始されるため、挿入した位置よりしたのDOMが追加ボタン押下と同時に下方にズレてしまう。

ここで、v-moveクラスを設定することにより、移動するDOMは指定した時間をかけて滑らかに移動することになる。

```vue
.fade-move{
  transform: transform 3s;
}
```
![b3f3da90f46718913132c1342d40a33c.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/97ef3c08-8283-8122-85c3-d829f92bf390.gif)

また、削除時については、アニメーションが完了するまで、削除対象のDOMは存在しているので、アニメーション完了後に、下方のDOMが詰めるという挙動になってしまっている。これは、CSSのposition:absoluteでDOMを浮かせて対応する。

```vue
.fade-leave-active {
  transition: opacity 3s;
  position: absolute;
}
```
これで、目的とする挙動が行われる。