Vue.jsで使われているFLIPアニメーションの仕組み

## はじめに

FLIPアニメーションは前の場所から次の場所にスムーズに移動させる仕組みで次の記事が発端のようです。

Aerotwist - FLIP Your Animations
https://aerotwist.com/blog/flip-your-animations/

けっこう前の記事なのでフロントエンドに詳しい方にはいまさら感があるのかもしれませんが初見の私にはとても興味深い内容だったのでどうやって動いているのか実際に試してみました。

## 仕組み

<p data-height="480" data-theme-id="0" data-slug-hash="wpPGQP" data-default-tab="result" data-user="akicho8" data-embed-version="2" data-pen-title="FLIP animation mechanism" class="codepen">See the Pen <a href="https://codepen.io/akicho8/pen/wpPGQP/">FLIP animation mechanism</a> by Akira Ikeda (<a href="https://codepen.io/akicho8">@akicho8</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>

```html
<!doctype html>
<html>
  <head>
    <style type="text/css">
      /* 移動させる物 */
      #box {
        width: 200px;
        height: 200px;
        background: blue;
      }

      /* 移動先 */
      .moved {
        position: relative;
        top:  200px;
        left: 200px;
      }
      
      /* 任意の移動方法 */
      .box-move {
        transition: all 1s 0s ease-in-out;
      }
    </style>
  </head>
  <body>
    <div id="box"></div>
    <script>
      // 移動させたい移動元の要素を取得
      const el = document.querySelector("#box")

      // そこから位置を取得
      const first = el.getBoundingClientRect()

      // moved クラスを付与して要素をいったん移動させる
      el.classList.add("moved")

      // すると移動先の位置が瞬時に求まる
      const last = el.getBoundingClientRect()

      // 移動先から見た移動元への差を求める
      // たとえば 0 から 100 なら -100 になる
      const invert_x = first.left - last.left
      const invert_y = first.top  - last.top

      // 今は移動先にいるので求めた差分を使って移動元までずらす
      el.style.transform = `translate(${invert_x}px, ${invert_y}px)`
       
      // ここが超重要
      // このフレームでアニメーションするとブラウザは「移動先」から「移動元」に
      // 移動したと考えてしまうため1フレーム待つ
      requestAnimationFrame(() => {
        // 1フレーム前に設定した移動元までずらす指定を消すことで移動先まで位置が戻る
        el.style.transform = ""
        // このときにアニメーションを有効にすると「移動」する (transformを消すタイミングは後でもよい)
        el.classList.add("box-move")
      })
    </script>
  </body>
</html>
```

- 当初、css側の移動先の指定を transform: translate() で書いていたら計算がややこしくなったので top, left を使った方がいい。おそらく記事の方も top, left を想定していると思われる。
- 同じフレーム内であっても class を指定した瞬間に位置は移動する
- 同じフレーム内であっても要素の style.transform に設定された内容を消しても瞬時に移動しない
- requestAnimationFrame() は1フレーム待つのに使える
- last には、まったく別の dom の位置を指定するようにすると別の dom に向かって移動するようなこともできた

## 同様の処理の Vue.js 版

<p data-height="480" data-theme-id="0" data-slug-hash="qpVNbZ" data-default-tab="result" data-user="akicho8" data-embed-version="2" data-pen-title="Vue.js transition-group (FLIP) animation" class="codepen">See the Pen <a href="https://codepen.io/akicho8/pen/qpVNbZ/">Vue.js transition-group (FLIP) animation</a> by Akira Ikeda (<a href="https://codepen.io/akicho8">@akicho8</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>

```html
<!doctype html>
<html>
  <head>
    <script src="https://unpkg.com/vue"></script>
    <style type="text/css">
      .box {
        width:  200px;
        height: 200px;
        background: blue;
      }
      .moved {
        position: relative;
        top:  200px;
        left: 200px;
      }
      .box-move {
        transition: all 1s 0s ease-in-out;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <button @click="moved = !moved">Toggle</button>
      <transition-group tag="div" name="box">
        <div :key="'box'" class="box" :class="{moved: moved}"></div>
      </transition-group>
    </div>
    <script>
      new Vue({
        el: '#app',
        data: {
          moved: false,
        },
      })
    </script>
  </body>
</html>
```

- css の部分は同じ
- Vue.js の場合は transition-group がうまくやってくれる
- key は決め打ちの "box" にしているけど実際は transition-group 内でユニークにしないといけない
- transition-group の name="(ここの名前)" に対応して css の .(ここの名前)-move の名前が決まる