アイテム一覧へアイテムを追加したときにシンプルなアニメーションを適用する

# 概要

アイテム一覧にアイテムを追加したときにフェードインのアニメーションを適用するデモアプリケーションです。
なおVue.jsのトランジション機能を利用したごく普通のアニメーションになります。

**環境**

* Windows 10 Professional
* Node.js 6.11.5
* Vue.js 2.5.16
    * Vuetify
* Visual Studio Code 1.23.0

**参考**

* [Enter/Leave とトランジション一覧] (https://jp.vuejs.org/v2/guide/transitions.html)
* [Animate.css] (https://daneden.github.io/animate.css/)

# アイテム一覧

* アイテムデータの持ち方とアイテムの追加処理はダミーです。

**アイテム一覧を表示するコンポーネント**

```vue:Cards.vue
<template>
  <div class="cards">
    <slot name="nav"></slot>
    <div id="e3" style="max-width: 500px; margin: 20px auto;" class="grey lighten-3">
      <v-card>
        <v-container fluid style="min-height: 0; text-align: left;" grid-list-lg>
          <transition-group name="card-anim">
            <v-layout row wrap v-for="(item, index) in items" v-bind:key="index">
              <component v-bind:is="cardType(item)" v-bind:item="item" />
            </v-layout>
          </transition-group>
        </v-container>
      </v-card>
      <div>
        <v-container fluid style="min-height: 0;">
          <v-layout row>
            <v-flex xs12>
              <v-btn color="success" v-on:click="addList">More</v-btn>
            </v-flex>
          </v-layout>
        </v-container>
      </div>
    </div>
    <slot name="footer"></slot>
  </div>
</template>

<script>
import NormalCard from '@/components/NormalCard'
import ImageCard from '@/components/ImageCard'

export default {
  name: 'Cards',
  components: {
    'normal-card': NormalCard,
    'image-card': ImageCard
  },
  data () {
    return {
      itemMaster: [],
      items: [],
      itemIndex: 0
    }
  },
  mounted () {
    this.itemMaster = [
      { title: 'ゼルダの伝説', description: '任天堂から発売されたゲームソフト。ゼルダの伝説シリーズの一作目にあたる。', color: 'green darken-4' },
      { title: 'リンクの冒険', description: '任天堂より昭和62年1月14日に発売されたファミリーコンピュータ ディスクシステム用アクションアドベンチャーゲーム。', color: 'light-green darken-4' },
      { title: 'ゼルダの伝説 神々のトライフォース', description: '平成3年11月21日に任天堂から発売されたスーパーファミコン用ゲームソフト。', color: 'green accent-4' },
      { title: 'ゼルダの伝説 夢をみる島', description: '任天堂から平成5年6月6日に発売されたゲームボーイ用アクションアドベンチャーゲーム。', color: 'orange darken-4' },
      { title: 'ゼルダの伝説 時のオカリナ', description: '平成9年11月21日に任天堂より発売されたNINTENDO64（N64）用アクションアドベンチャーゲーム、アクションRPG。', color: 'lime darken-4' },
      { title: 'ゼルダの伝説 ムジュラの仮面', description: '任天堂より平成12年4月27日に発売されたNINTENDO64用3DアクションRPG。', color: 'deep-purple darken-4' }
    ]
    this.items.push(this.itemMaster[0])
    this.items.push(this.itemMaster[1])
    this.itemIndex = this.items.length
  },
  methods: {
    addList () {
      if (this.itemMaster.length > this.itemIndex) {
        this.items.push(this.itemMaster[this.itemIndex++])
      }
    },
    cardType (item) {
      if (item.image) {
        return ImageCard
      } else {
        return NormalCard
      }
    }
  }
}
</script>

<style scoped>
.card-anim-enter-active {
  animation: fadeInUp .7s;
  animation-delay: .4s;
  opacity: 0;
}

@keyframes fadeInUp {
  0% {
    transform: translateY(60px);
    opacity: 0;
  }
  60% {
    opacity: .3;
  }
  100% {
    transform: translateY(0px);
    opacity: 1;
  }
}
</style>
```

**アイテム1件分を表示するコンポーネント**

```vue:NormalCard.vue
<template>
  <v-flex xs12>
    <v-card v-bind:color="item.color" class="white--text" v-bind:tile="false" v-bind:hover="true">
      <v-card-title primary-title>
        <div class="headline">{{ item.title }}</div>
        <div>{{ item.description }}</div>
      </v-card-title>
      <v-card-actions>
        <v-btn flat dark>Details</v-btn>
      </v-card-actions>
    </v-card>
  </v-flex>
</template>

<script>
export default {
  name: 'NormalCard',
  props: {
    'item': {
      type: Object,
      required: true
    }
  }
}
</script>
```

## アニメーションのポイント

**その1**

繰り返し要素にアニメーションを適用するにはtransition-groupを使用します。

```vue
<transition-group name="card-anim">
  <v-layout row wrap v-for="(item, index) in items" v-bind:key="index">
    <component v-bind:is="cardType(item)" v-bind:item="item" />
  </v-layout>
</transition-group>
```

**その2**

Vue.jsのトランジションには決められた6つのCSSクラスがあります。クラス名にはtransition又はtransition-groupのname属性がprefixとして付きます。（name属性を省略すると`v-`になります。）

この記事の例では、transition-groupのname属性に"card-anim"を指定してるので、6つのクラス名は以下のようになります。

* card-anim-enter
    * enterの開始状態
* card-anim-enter-active
    * enterの活性状態
* card-anim-enter-to
    * enterの終了状態 
* card-anim-leave
    * leaveの開始状態
* card-anim-leave-active
    * leaveの活性状態
* card-anim-leave-to
    * leaveの終了状態

それぞれの状態時に発生させるアニメーションをCSSで定義します。この例ではアイテム追加時に下(60pxずれた位置)から上へ縦方向にフェードインするアニメーションを適用しています。
`translateY`を`translateX`に変えると横方向にフェードインするようになります。

```css
.card-anim-enter-active {
  animation: fadeInUp .7s;
  animation-delay: .4s;
  opacity: 0;
}

@keyframes fadeInUp {
  0% {
    transform: translateY(60px);
    opacity: 0;
  }
  60% {
    opacity: .3;
  }
  100% {
    transform: translateY(0px);
    opacity: 1;
  }
}
```

アイテムを削除したときにフェードアウトのアニメーションを適用したい場合は、下記のように"leave-active"のanimationにreverseを付けてエフェクトを反転させます。

```css
.card-anim-leave-active {
  animation: fadeInUp .7s reverse;
}
```

## アニメーションの様子

アニメーションの様子をgifにしました。
最初に2件表示されている状態から、MOREボタンを押して1件ずつアイテムを追加している様子です。

---

![xee2.gif](https://qiita-image-store.s3.amazonaws.com/0/22772/e2b2836e-4653-51e9-1668-2939ebf838b3.gif)

---