CSS transitionで頑張らないVue.js transition

こちらの記事は [Vue.js #3 Advent Calendar 2017](https://qiita.com/advent-calendar/2017/vue3) 16日目の記事です。

初めまして。フリーランスでフロントエンドエンジニアをやっているまさあき(@masaakikunsan)です。
本日の記事では、Vue.jsにおける魅力的なアニメーションのための機能 **transition** についてご紹介したいと思います。

# TL;DR

- Vue.jsのtransitionを利用するとv-ifで気軽にアニメーションの管理ができる
- transitionの利用は、アニメーションさせたい要素を `<transition>` `<transition-group>` タグで囲む
- transition-groupを利用することで複数の要素が連なる場合の管理もできる

# CSSとクラス切り替えだけで行うトランジションのつらさ

みなさんJavaScriptで動的に変わる要素にアニメーションをつけるとき、主にCSS側でアニメーションを書いてJSでクラスを切り替えることが多いかと思います。

例えば、Vue.jsで、ボタンを押した時に簡単なフェードを実装する場合ですと、このようになるかと思います。

```html:CSSとクラス切り替えだけのサンプル
<template>
  <div>
    <button @click="show = !show">
      押して
    </button>
    <div :class="{'is-show': show}" class="fade">
      <p>加藤恵</p>
    </div>
  </div>
</template>

<style scoped>
.fade {
  opacity: 0.0;
  transition: all .5s ease-out;
}

.fade.is-show {
  opacity: 1.0;
}
</style>
```

これで実際に表示されるかと思いますが、以下のような問題があるかと思います。

- 常にDOM上に要素自体は表示されるため、v-ifなどでシンプルに記述できず、記述がわかりづらくなる
- コンポーネントなどの表示を切り分ける場合、 `created()` が表示前に実行されるため、「開いた時にcreatedしたい」みたいなことができない

こういった問題を解決するために、Vue.jsには transition という機能があるためご紹介します。

# transitionラッパーコンポーネント
Vue.jsでは、上記のようなアニメーションを制御するためのコンポーネントとして、`<transition>` というラッパーコンポーネントを提供しています。
このコンポーネントを使用することで、以下のような要素やコンポーネントに entering/leaving トランジションを追加できます。

- 条件付きの描画(v-if)
- 条件付きの表示(v-show)
- 動的コンポーネント
- コンポーネントルートノード (Component root nodes)

# transitionによって付与されるクラス

Enter（表示される時）/Leave（消える時）に用意されているそれぞれのクラス名を使用することでアニメーションが簡単に作れます。
各クラスはtransitionの名前が先頭に付き、名前がない場合はデフォルトで `v-` というプレフィックスが付きます。
名前がある場合はデフォルトの `v-` が自分でつけた `name-` に変わります。
細かいclassの説明は公式をご覧ください。(https://jp.vuejs.org/v2/guide/transitions.html#トランジションクラス)

下記のような簡単なアニメーションを作って見ましょう
[![Screenshot from Gyazo](https://gyazo.com/673c2f2a435b475b7b5fcef75cd6ab76/raw)](https://gyazo.com/673c2f2a435b475b7b5fcef75cd6ab76)

```html:名前がない場合
<template>
  <div>
    <button @click="show = !show">
      押して
    </button>
    <transition>
      <p v-if="show">加藤恵</p>
    </transition>
  </div>
</template>

<style scoped>
.v-enter-active, .v-leave-active {
  transition: opacity .5s
}

.v-enter, .v-leave-to {
  opacity: 0
}
</style>
```

```html:名前がある場合
<template>
  <div>
    <button @click="show = !show">
      押して
    </button>
    <transition name="megumi">
      <p v-if="show">加藤恵</p>
    </transition>
  </div>
</template>

<style scoped>
.megumi-enter-active, .megumi-leave-active {
  transition: opacity .5s;
}

.megumi-enter, .megumi-leave-to {
  opacity: 0;
}
</style>
```

# 初期描画時のtransition
transitionコンポーネントに```appear```属性を追加することで、ノードの初期描画時にtransitionを適用できます

```html:

<template>
  <div>
    <button @click="show = !show">
      押して
    </button>
    <transition appear>
      <p v-if="show">加藤恵</p>
    </transition>
  </div>
</template>

<style scoped>
.v-enter-active, .v-leave-active {
  transition: opacity .5s;
}

.v-enter, .v-leave-to {
  opacity: 0;
}
</style>
```

# transition-group

v-forのように同時に描画したいリストのアイテムがある場合、transition-groupコンポーネントを使うと良いです。

# transition-groupの仕様

- transitionと異なり、実際に要素を描画するときデフォルトでspanが描画されます。この要素は、```tag```属性で任意の要素に変えることができます。

```html:tag指定なし
<template>
    <transition-group>
        <span v-for="(item, key) for items" :key="key">{{item}}</span>
    <transition-group>
</template>

<script>
export default {
  data () {
    return {
      items: ['a', 'a', 'a', 'a', 'a']
    }
  }
}
</script>
```

<img width="269" alt="スクリーンショット 2017-12-16 17.43.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/186521/0d5ef798-92c4-89db-7e87-af4cc48bdb41.png">

```html:tag指定あり
<template>
    <transition-group tag="div">
        <span v-for="(item, key) for items" :key="key">{{item}}</span>
    <transition-group>
</template>

<script>
export default {
  data () {
    return {
      items: ['a', 'a', 'a', 'a', 'a']
    }
  }
}
</script>
```

<img width="257" alt="スクリーンショット 2017-12-16 17.43.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/186521/0952b50d-fb64-7d4b-9b3a-566cd72bff93.png">


- 中の要素は、key 属性を持つことが<span style="color: red;">必須</a>です。

実際の例でどのように使うか見て見ましょう。
formなどでaddやremoveボタンで要素を追加したり消したりしたいときがあると思います。そのような時に使えます。
[![Screenshot from Gyazo](https://gyazo.com/c9f0e3f9e4ac188fc4eb88c2f751aae8/raw)](https://gyazo.com/c9f0e3f9e4ac188fc4eb88c2f751aae8)

```html:
<template>
  <div>
    <transition-group tag="form" appear>
      <div v-for="(value, key) in formData" :key="key" class="flex">
        <input
          type="text"
          v-model="formData[key].heroineName"
          placeholder="ヒロインの名前"
        >
        <button @click="removeBtn(key)">
          Remove
        </button>
      </div>
    </transition-group>
    <button @click="addBtn()">
      Add
    </button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      formData: [
        {
          heroineName: ''
        }
      ]
    }
  },
  methods: {
    addBtn () {
      this.formData.push({heroineName: ''})
    },
    removeBtn (key) {
      if (this.formData.length !== 1) {
        this.formData.splice(key, 1)
      }
    }
  }
}
</script>

<style scoped>
.flex {
  display: flex;
}

.v-enter-active {
  transition: all .8s ease;
}

.v-leave-active {
  transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}

.v-enter, .v-leave-to {
  transform: translateX(10px);
  opacity: 0;
}
</style>
```

# まとめ

ここまでVue.jsのtransitionの基礎的な部分についてご紹介いたしましたが、いかがでしたか？

transitionは奥が深い機能ですが、基本的なアニメーションについては、ここまでに挙げた機能だけで十分に実用可能です。
アプリケーションにワンポイントのアニメーションを入れたい時には、是非使ってみてください。

もっと知りたい人は公式をご覧ください（https://jp.vuejs.org/v2/guide/transitions.html）

今回は基礎的な内容しか書いていないので次回はもう少し色々紹介したいな思っています。
Vue.jsのtransitionで楽しい生活をお送りください！

# 参考資料

- 公式資料
  - https://jp.vuejs.org/v2/guide/transitions.html
- トランジションに付与されるクラス
  - https://jp.vuejs.org/v2/guide/transitions.html#トランジションクラス