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


こちらの記事は Vue.js #3 Advent Calendar 2017 16日目の記事です。

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

TL;DR

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

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

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

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

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#トランジションクラス)

下記のような簡単なアニメーションを作って見ましょう

名前がない場合
<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>
名前がある場合
<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を適用できます


<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属性で任意の要素に変えることができます。
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>

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>

  • 中の要素は、key 属性を持つことが必須です。

実際の例でどのように使うか見て見ましょう。
formなどでaddやremoveボタンで要素を追加したり消したりしたいときがあると思います。そのような時に使えます。

<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で楽しい生活をお送りください!

参考資料