パッとできる！Vue.jsを使ってカードの手札を広げるアニメーションを表現してみる

## はじめに

Vue.jsを使って、トランプのカードを広げるような表現を作ってみました。
v-forやクラス・スタイルバインィングなどを使うことで、
簡単に実装することができます。

早速仕組みを説明しつつ、コードを見せていきます。

![aperture-video-67b30651-2db8-456f-9068-56d8d0797c54.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258186/37b80503-c58e-258f-46e2-362039b080cf.gif)

## 骨組みを作成

まずは、カードを枚数分重ねた状態で表示、
カードを広げるためのボタン、リセット用のボタンと、
手札を広げた時にわかりやすいように色をバインディングしておきます。

今回はRGB値の緑と青の値を
Indexごとに20倍して、16進数に直して表現してます。（適当）

重なりを表現するために、positionはabsoluteで表現しており、
最初の数が一番上に来るように、z-indexをバインディングし、
変数にカードの上限（maxNum）を用意して、
Indexから引いていきます。

v-forでカードの枚数分ループを回して、
div要素を連ねていきます。

transform-originは下記画像のような形を起点に取るため
X: 25%, Y: 100%に設定します。
<img width="1098" alt="kakudo.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258186/42c28c2f-722e-7667-88e1-5f3d7a9be333.jpeg">

```vuejs:AnimationDemo.vue
<template>
  <div class="container">
    <h1>Card Demo</h1>
    <div class="demo-box">
      <!-- カードを表示 -->
      <div
        class="card" :class="'card-' + i"
        v-for="i in cardNum" :key="i"
        :style="{
          'background': '#0' + colorDefined(i),
          'z-index': Number(maxNum - i)
        }">
        {{ i }}
      </div>
    </div>
    <div>
      <button class="button" @click="deckOpen()">OPEN</button>
      <button class="button" @click="deckReset()">RESET</button>
    </div>
  </div>
</template>

<script>
export default {
  name: 'AnimationDemo',
  data () {
    return {
      cardNum: 20, // カードの枚数
      maxNum: 100, // カードの限界値
      maxDeg: 45, // 手札を広げる時の角度のMAX限界値
      minDeg: -45, // 手札を広げる時の角度のMIN限界値
    }
  },
  methods: {
    // 色の定義（わかりやすくしたかったので）
    colorDefined (num) {
      return ('00' + (num * 20).toString(16).toUpperCase()).substr(-2)
    },
    // 手札のオープン
    deckOpen () {
      //dummy
    },
    // 元に戻す
    deckReset () {
      //dummy
    }
  }
}
</script>

<style scoped>
.container {
  height: 100vh;
  width: 100vw;
  overflow: hidden;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}
.demo-box {
  width: 800px;
  height: 500px;
  display: flex;
  justify-content: space-around;
  align-items: center;
  flex-wrap: wrap;
  border: solid;
}
.card {
  position: absolute;
  width: 150px;
  height: 250px;
  border-radius: 15px;
  border: double 5px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 24px;
  font-weight: 800;
  color: white;
  transition: 0.5s;
  transform-origin: 25% 100%;
}
.button {
  cursor: pointer;
  margin: 15px;
  padding: 5px 15px;
  border: none;
  font-size: 24px;
  border: solid;
  border-radius: 5px;
}
</style>
```

## ロジックを作成

次にカードをオープンした際の表現を書いていきます。
カードは 
手札を広げる時の角度のMAX限界値 - 手札を広げる時の角度のMIN限界値
から、カードの枚数分差分を割出していきます。

今回は限界値の差を求めると９０度になるため、
その分、カードの枚数を割り算して、差分を出します。

<img width="891" alt="sabun.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258186/040a44c9-94e8-a1dd-cd2b-f924dc5b83c5.jpeg">

```vue:AnimationDemo.vue

<script>
export default {
  name: 'AnimationDemo',
  data () {
    return {
      cardNum: 20, // カードの枚数
      maxNum: 100, // カードの限界値
      maxDeg: 45, // 手札を広げる時の角度のMAX限界値
      minDeg: -45, // 手札を広げる時の角度のMIN限界値
    }
  },
  methods: {
    // 色の定義（わかりやすくしたかったので）
    colorDefined (num) {
      return ('00' + (num * 20).toString(16).toUpperCase()).substr(-2)
    },
    // 手札のオープン
    deckOpen () {
      // カードの枚数から、それぞれの角度の差分値を割り出す
      var deltaDeg = (this.maxDeg - this.minDeg) / this.cardNum
      for (var i = 0; i < this.cardNum; i++) {
        var el = document.querySelector('div.card-' + Number(i + 1))
        // 一番角度を開いた先から、差分値分Indexを掛けて、角度を割り出す
        var deg = (this.maxDeg - (i + 1) * deltaDeg)
        el.style.transform = 'rotateZ(' + deg +'deg)'
      }
    },
    // 元に戻す
    deckReset () {
      for (var i = 0; i < this.cardNum; i++) {
        var el = document.querySelector('div.card-' + Number(i + 1))
        el.style.transform = 'rotateZ(0deg)'
      }
    }
  }
}
</script>

```

## 完成

いかがでしょうか？
やってみたい表現をそれぞれ、簡易化して差分値を当てはめることで、
アニメーションを表現することができると思いますので、
また、別の表現なども記事化できたらと思います。

現在弊社では、HRモンスターと呼ばれる
採用の新しいスタイルを提供するサービスをローンチいたしました。

ローンチ後のさらなる機能追加、改善などのPDCAサイクルを回すべく、
エンジニアを募集しております。
https://www.wantedly.com/projects/341182

Kubernetes、Vue.js（Javascript）、Django（Python）といったモダンな技術を使って、
開発しておりますので、もしご興味がある方はぜひ、ご応募お待ちしております。