【CSS】ホバーすると立体的にアニメーションするカードを作る


## はじめに
みなさんは、どんなトレーディングカードゲームをやったことがありますか？

僕が触れたことがあるトレーディングカードゲームは、**遊戯王**、**デュエマ**、**ポケカ**くらいですが、他にも、1993年からある**マジック:ザ・ギャザリング**や最近の**ワンピース** などなどいろいろなトレーディングカードゲームが存在します。

ただ、web上でトレーディングカードゲームのカードを表現しようとしても画像で表現することになり、立体感に欠け物足りない感じです。

今回は、そんなトレーディングカードゲームのカードを立体感に見るように、CSSとJavaScriptを使って、ホバーすると立体的にアニメーションするカードを作っていこうと思います。

## 完成形
完成形は、↓こんな感じです。
<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="jOKYdbj" data-user="sp_degu" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/sp_degu/pen/jOKYdbj">
  3D  Card</a> by でぐぅー | Qiita (<a href="https://codepen.io/sp_degu">@sp_degu</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

hoverすると、マウスの位置に合わせて、カードが手前側に出ているよう見えるアニメーションにすることで、立体的にアニメーションするように表現しています。

また、CSSのプロパティの観点では、`preserve-3d`で、3Dに回転させて、画像を `translate` を使ってずらすことで、立体的にアニメーションして見えるように表現しています。

詳しい作り方は、これから解説していきます。

## 作り方

### 1. 構成を理解する
まずは、今回作成する立体的にアニメーションして見えるような表現の構成解説していきます。

![image1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390720/82f12dc6-dcea-0080-0d4c-517525c3e871.png)

立体的にアニメーションして見えるような表現は、↑このように `transform-style: preserve-3d;`で、X軸とY軸を回転させることで、それっぽくしています。

ただ、`transform-style: preserve-3d;`だけを動かすと、回転方向がわからなくなるため、
**回転に合わせて、画像の位置を調整する**必要があります。

### 2. HTMLを作成する
1の構成になるように、HTMLの構造を以下のように作成していきます。

```sample.html
<div class="card-wrapper">
  <img class="card image" src="../../image.jpeg" />
</div>
```

画像が見える位置が `:hover` に合わせて変わるため、
`<div class="card-wrapper">` で `<img>`をラップします。

そのため、３Dに回転する要素は、`<div class="card-wrapper">` になります。

### 3. カードの形を整える
次に、`<img>` にscaleをかけて、`<div class="card-wrapper">`から見えない部分を作り、
 `:hover` に合わせて、画像の見える位置を変えられるように、形を整えて行きます。

```sample.css
/*画像をcard-wrapperより大きくする*/
.image {
  scale: 1.2;
}

/*形を整える*/
.card {  
  aspect-ratio: 3 / 4;
  box-shadow: 0px 4px 8px rgba(0, 0, 0, .2);
  height: 100%;
  position: absolute;
  width: 100%;
}

.card-wrapper {
  border-radius: 4.5% / 3.5%;
  aspect-ratio: 3 / 4;
  display: grid;
  height: calc(100% - 32px);
  max-width: 100%;
  overflow: hidden;
  position: absolute;
}

/*真ん中に持ってきている*/
body {
  background-color: #212529;
  display: grid;
  height: calc(100vh - 32px);
  margin: 0;
  padding: 16px;
  place-items: center;
  position: relative;
  width: calc(100% - 32px);
  transform-style: preserve-3d;
}
```

### 4. マウスの位置に応じた、アニメーションを作成する
#### カード内のマウスの位置を取得する
```sample.js
console.clear()

const CARD = document.querySelector('.card-wrapper')

const UPDATE = ({ x, y }) => {
  const BOUNDS = CARD.getBoundingClientRect()
  const posX = x - BOUNDS.x
  const posY = y - BOUNDS.y
  const ratioX = posX / BOUNDS.width
  const ratioY = posY / BOUNDS.height
  CARD.style.setProperty('--ratio-x', ratioX)
  CARD.style.setProperty('--ratio-y', ratioY)
}

document.body.addEventListener('pointermove', UPDATE)
```
`addEventListener` の `'pointermove'` で、マウスポインターの位置を取得します。

また、マウスポインターがカード内のどの位置にあるのかをいい感じに計算しした値を
cssの変数(`--ratio-x`・`--ratio-y`)で使えるように、`.card-wrapper`のstyle属性に定義して、います。

以下の画像のように計算しています。
![image4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390720/763243e6-543f-fb7a-a549-fd9f83ea57c7.png)


#### カードがマウスの位置に応じて回転するようにする
```sample.css
.card-wrapper:hover {
  transform:
    rotateX(calc((var(--ratio-x) - 0.5) * 50deg))
    rotateY(calc((var(--ratio-y) - 0.5) * -50deg));
}
```

`.card-wrapper` は、マウスポインターの位置に応じて、`rotateX`・`rotateY` でカードを回転させています。
ホバーしている位置を手前側に持ってきたいので、以下のような数式になります。

```
X軸 → calc((var(--ratio-x) - 0.5) * 50deg)
Y軸 → calc((var(--ratio-y) - 0.5) * -50deg)
```

- **--ratio-x** / **--ratio-y**
    - マウスポインターの位置がカードのX軸/Y軸の中で**左上の位置から**それぞれ何％の位置になるか
    - 値：`0` ~ `1`
- **var(--ratio-x) - 0.5**　/ **var(--ratio-y) - 0.5**
    - マウスポインターの位置がカードのX軸/Y軸の中で**中心から**何％の位置になるか
    - 値：`-0.5` ~ `0.5`
- **(var(--ratio-x) - 0.5) * 50deg** / **(var(--ratio-y) - 0.5) * 50deg**
    - **最大の角度を25°**になるようにしています。
    - Y軸は、回転方向を逆にするため、`-50deg`をかけています。
    - 値：`-25°` ~ `25°`

#### 画像がマウスの位置に応じて移動するようにする
```sample.css
.image:hover {
  translate:
    calc((var(--ratio-x) - 0.5) * -5%)
    calc((var(--ratio-y) - 0.5) * -5%);
}
```

マウスの位置に応じて、画像の位置をずらすことで、回転方向が分かりやすくなるため、`<img>`を以下のような数式で移動させています。

```
X軸 → calc((var(--ratio-x) - 0.5) * -5%)
Y軸 → calc((var(--ratio-y) - 0.5) * -5%)
```

- **--ratio-x** / **--ratio-y**
    - マウスポインターの位置がカードのX軸/Y軸の中で**左上の位置から**それぞれ何％の位置になるか
    - 値：`0` ~ `1`
- **var(--ratio-x) - 0.5**　/ **var(--ratio-y) - 0.5**
    - マウスポインターの位置がカードのX軸/Y軸の中で**中心から**何％の位置になるか
    - 値：`-0.5` ~ `0.5`
- **(var(--ratio-x) - 0.5) * -5%** / **(var(--ratio-y) - 0.5) * -5%**
    - 移動方向を逆にするため、`-5%`をかけています。
    - **最大の移動が2.5%** になるようにしています。
    - 値：`-2.5%` ~ `2.5%`

### 完成
すると、このようにマウスの位置に合わせて、カードが立体的にアニメーションしているようにに表現しています。

<p class="codepen" data-height="300" data-default-tab="css,result" data-slug-hash="jOKYdbj" data-user="sp_degu" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/sp_degu/pen/jOKYdbj">
  3D  Card</a> by でぐぅー | Qiita (<a href="https://codepen.io/sp_degu">@sp_degu</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## まとめ
今回は、トレーディングカードゲームのカードを立体感に見るように、CSSとJavaScriptを使って、ホバーすると立体的にアニメーションするカードを使って作りました。

構造としては、シンプルですが、計算式が直感で理解するのは難しく感じます。
そのため、もしお時間あれば手元で計算式の値を変えてみていただくと、理解が深まると思います。

この表現がトレーディングカードゲームのカードのweb通販のサイトや、クレジットカードの比較アフィリエイトサイトとかで、使われると見るだけで楽しいものになるのかなとかと感じました。

またこの記事のカードを光らせるアニメーションを追加するとなおクオリティが上がると思います。

https://qiita.com/degudegu2510/items/1821047debbd58384117

---

最後まで読んでくださってありがとうございます！

普段はデザインやフロントエンドを中心にQiitaに記事を投稿しているので、ぜひQiitaのフォローとX(Twitter)のフォローをお願いします。

https://twitter.com/degudegu2510