Help us understand the problem. What is going on with this article?

Vue.jsでいい感じのアニメーションを作りたい

この記事は クラウドワークス Advent Calendar 2019 の3日目の記事です。

昨日は、@minamijoyo さんによるtfupdateでTerraform本体/プロバイダ/モジュールのバージョンアップを自動化する でした!

はじめに

こんにちは、最近アルコールに負け続けている新卒エンジニアの @b0ntenmaru です。
Rubyの会社に入社して7ヶ月ほど経ったのですが、この7ヶ月間あまり Ruby は書かずに Vue.js を利用したフロントエンド開発ばかりしていました。

その中でも Vue.js でのアニメーションの実装は経験がなく、一から調べる機会があったので今回はそれについて書いていこうと思います。

基本的なこと

Vue.js はデフォルトでトランジション( transition )という機能を提供していて、それを利用することでいい感じにアニメーションを実装することができます。

まずここにボタンを押したら div.hoge を表示/非表示するだけの 単一ファイルコンポーネント があります。いい感じのアニメーションはありません。

<template>
  <div id="app">
    <button @click="show = !show">
      click!
    </button>

    <!--  アニメーションで表示/非表示が切り替わる時にふわっと表示させたい要素 -->
    <div class="hoge" v-if="show">
      hogehoge
    </div>
  </div>
</template>

<script>
export default {
  name: 'app',
  data() {
    return {
      show: false
    }
  }
}
</script>

See the Pen VwwJmBq by Date (@b0ntenmaru) on CodePen.

いい感じのアニメーションを追加する

アニメーションを動作させるためには該当の要素(ここでは div.hoge )を <transition> タグで囲み、トランジションクラスに対して CSS を指定します。トランジションクラスに関しては後述します。

<transition>
  <div class="hoge" v-if="show">
    hogehoge
  </div>
</transition>
<style>
/* 以下の v-enter, v-enter-to, v-enter-active がトランジションクラス */

/* 表示アニメーションをする前のスタイル */
.v-enter {
  opacity: 0;
}

/* 表示アニメーション後のスタイル */
.v-enter-to {
  opacity: 1;
}

/* 表示アニメーション動作中のスタイル */
.v-enter-active {
  transition: all 500ms;
}
</style>

これにより、要素の表示に動きを与えることができました。

See the Pen dyyxrJr by Date (@b0ntenmaru) on CodePen.

トランジションクラス

トランジションクラスは、 <transition> タグで要素を囲むことで使用できるようになるCSSのクラスです。
ちょうど上で登場した v-enter , v-enter-to , v-enter-active がトランジションクラスにあたります。

トランジションクラスには Enter と Leave の2つのフェーズが存在し、

  • <transition> タグで囲った要素を表示する時を Enter
  • <transition> タグで囲った要素を非表示にする時を Leave

と言います。(上のサンプルでは Enter のみ)

この2つのフェーズにはそれぞれ、アニメーションの動作前・動作中・動作後の3つ状態に対応した3つのクラスがあります。

クラス 要素の状態
v-enter 表示アニメーションの開始時のスタイル
v-enter-to 表示アニメーションの終了時のスタイル
v-enter-active 表示アニメーション中のスタイル
v-leave 非表示アニメーションの開始時のスタイル
v-leave-to 非表示アニメーションの終了時のスタイル
v-leave-active 非表示アニメーション中のスタイル

02.png

いい感じの非表示アニメーションを追加する

続いて非表示アニメーションを実装します。
Enter の時とやることは逆ですが、下記のように指定すると非表示アニメーションが動いてくれます。

/* 非表示アニメーション動作前のスタイル */
.v-leave {
  opacity: 1;
}

/* 非表示アニメーション動作後のスタイル */
.v-leave-to {
  opacity: 0;
}

/* 非表示アニメーション動作中のスタイル */
.v-leave-active {
  transition: all 500ms;
}

See the Pen yLyBLRP by Date (@b0ntenmaru) on CodePen.

このトランジションクラスが Vue でアニメーションを実装するための基本となります。

複数要素のトランジション

続いて、v-for など同時に描画された複数の要素にアニメーションを適応させるためのリストトランジションについて説明します。

下記の単一ファイルコンポーネントを例とします。
現状 hoge が一覧されており、ADD ボタン押下で新しい hoge が一覧に追加・ hoge 横の x ボタン押下で一覧から削除できる仕様となっています。

<template>
  <div id="app">
    <button @click="add">ADD</button>
    <div v-for="(todo, index) in todos" :key="todo.key">
      <span>{{ todo.value }}</span><input @click="remove(index)" type="button" value="x" />
    </div>
  </div>
</template>

<script>
export default {
  name: 'app',
  data() {
    return {
      todos: [
        {key: 0, value: 'hoge'},
        {key: 1, value: 'hoge'},
        {key: 2, value: 'hoge'},
      ],
      nextNum: 2
    }
  },

  methods: {
    add: function() {
      const value = 'hoge'
      const todo = {
        key: this.nextNum += 1,
        value
      }
      this.todos.push(todo)
    },
    remove: function(index) {
      this.todos.splice(index, 1)
    }
  }
}
</script>

See the Pen LYEPxyJ by Date (@b0ntenmaru) on CodePen.

いい感じのアニメーションを適応させる

hoge 追加時/削除時にアニメーションを適応させます。
やることとしては単一要素のトランジションと同じでですが、今回のような複数の要素にアニメーションを適応させる時は <transition-group> タグで囲い、フェーズ( Enter / Leave )ごとにスタイルを書いていきます。

<transition-group>
  <div v-for="(todo, index) in todos" :key="todo.key">
    <span>{{ todo.value }}</span><input @click="remove(index)" type="button" value="x" />
  </div>
</transition-group>
<style>
/* 表示・非表示アニメーション中 */
.v-enter-active, .v-leave-active {
  transition: all 500ms;
}

/* 表示アニメーション開始時 ・ 非表示アニメーション後 */
.v-enter, .v-leave-to {
  opacity: 0;
}
</style>

See the Pen VwYZPNo by Date (@b0ntenmaru) on CodePen.

一点自分がハマったポイントがあるのでこちらも是非読んでみてください。

これで複数要素のアニメーションが実装できました。
ですが削除ボタン押下後、要素が移動する時にアニメーションがなく、新しい位置に味気なく移動してしまっています。

要素移動のトランジション

<transition-group> は Enter / Leave だけでなく、要素の移動のためのトランジションクラスである v-move クラスを使うことで上のような味気ない移動にアニメーションを加えることができます。

やることとしては、簡単で以下の2点を追記します。

/* 要素が移動する時に700msで移動するように指定 */
.v-move {
  transition: all 700ms;
}

.v-leave-active {
  /* 移動のトランジションをさせる場合は非表示アニメーション中に position: absoluteを指定しないと正しく動作しない */
  position: absolute;
}

See the Pen jOENGvN by Date (@b0ntenmaru) on CodePen.

これで複数要素の表示/非表示/移動時のアニメーションが実装できました。

ちなみにこの v-move に関しては要素のあらゆる移動の時に適応されるので、複数要素をシャッフルさせる機能を追加してもいい感じに動いてくれます。

See the Pen yLyBxZQ by Date (@b0ntenmaru) on CodePen.

ハマったポイント

配列のインデックスをキーにv-bindしてはダメ

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

Vue の公式にも記述されているように、 <transition-group> タグ内部の v-for で指定された要素はそれぞれが key を持つことが必須ですが、ここに配列(ここでは todos )の index を渡してはいけません、アニメーションが正常に動作しなくなります。
下記はkeyに配列の index を渡した例です。
削除ボタンを押すと決まって最後の要素が削除されたように見えてしまっています。

<transition-group>
  <div v-for="(todo, index) in todos" :key="index">
    <span>{{ todo.value }}</span><input @click="remove(index)" type="button" value="x" />
  </div>
</transition-group>

See the Pen GRgKXaE by Date (@b0ntenmaru) on CodePen.

Vue.js の key はどの値に変更があったのかを追うために使われていて、 index を key に指定した場合、最後より前の要素を消すことによって index の値が更新され、 Vue がどの要素をアニメーションさせれば良いかわからなくなり、一番最後の要素が消えるような挙動となってしまうようです。なので key には配列の index ではない一意な値を渡してあげましょう。

終わりに

以上、「Vue.jsでいい感じのアニメーションを作りたい」でした。

アニメーションがいい感じかどうかはさておき、 Vue.js でも簡単にアニメーションを実装できることがお分りいただけたかと思います。
Vue.js のアニメーションには状態のトランジションなどもあるので是非いろいろ触ってみてください!

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away