マグロにブラウザ上を気持ちよく泳いでもらうためのアニメーション選定

<a href="https://codepen.io/noplan1989/full/yzeGVE" target="_blank"><img src="https://qiita-image-store.s3.amazonaws.com/0/89103/b6f4657f-e991-0ace-8af6-03c94f6c5fc4.gif" width="300px"></a>

ブラウザの上をマグロに泳いでもらいたいと思ったとき、
気持ちよさそうに動いてもらえるのはどんな実装なのか。
CSSとJSのアニメーションライブラリを比較して選んでいきたいと思います。

# CSSかJSを選ぶ基準
## 軽いアニメーションならCSS
ホバーや要素のフェードイン・アウト、単純な移動や回転の場合は、
CSSのtransitionかanimationでやったほうが、
実装が楽なうえに動作が軽い場合が多いです。（特にスマホで）

## 複雑なアニメーションならJS
複雑なアニメーションが必要なときや、
タイムラインに沿ったアニメーションを実装する場合などには、
JSで書いたほうが柔軟性が高く、読みやすいです。

## マグロのアニメーションは？
マグロのアニメーションを実装する場合、
そういった定石は通用しないので、感覚的にマグロが活き活きしているように思えるものを選択していく必要があります。

# 比較方法
すべてのアニメーションを同時に再生して見比べ、
最もしっくりくるもの（主観）を採用します。

同時に再生する際に、それぞれのアニメーションをループで実装することも可能ですが、
一度ズレ始める全体的にバラバラになってしまうので、

同時に再生 => Promiseですべての終わりを待つ => 同時に再生

の流れで、アニメーションの比較をしやすい形で実装します。

setIntervalで適当な時間を空けても似たような実装はできますが、
なんだかあれなので採用しませんでした。

# 実装

## Promiseで終わりを待ってループ
```js
const infiniteTuna = (animationPromises) => {
  Promise.all(animationPromises.map(p => p()))
    .then(() => {
      infiniteTuna(animationPromises)
    })
}

infiniteTuna([
  cssPromise,
  tweenPromise,
  velocityPromise,
  animePromise
])
```

それぞれのアニメーションで `Promise` を返すようにして、
`Promise.all` に渡してアニメーションの終了を待ってループさせます。

## CSSアニメーション
```css
.tuna.is-active {
  animation: anime 1.2s cubic-bezier(0.645, 0.045, 0.355, 1); /* easeInOutCubic */
}

@keyframes anime {
  0% {
    transform: translate3d(100vw, 0, 0); /* 表示領域の幅（右の画面外） */
  }
  100% {
    transform: translate3d(-100%, 0, 0); /* 自分の幅（左の画面外） */
  }
}
```

```js
const cssPromise = () => {
  return new Promise(resolve => {
    const el = document.getElementById('css')
    const activeClass = 'is-active'

    const onAnimationEnd = () => {
      el.removeEventListener('animationend', onAnimationEnd)
      el.classList.remove(activeClass)
      setTimeout(() => resolve(), 0) // クラスのremove => addを意図的に遅らせる
    }

    el.addEventListener('animationend', onAnimationEnd)
    el.classList.add(activeClass)
  })
}
```
クラスを付けてアニメーションさせて、終わったらクラスを外します。
本来はイベントリスナにwebkitも指定するべきですが、
そういった些細なことはマグロの前では無力なので気にしないことにします。

https://developer.mozilla.org/ja/docs/Web/CSS/CSS_Animations/Using_CSS_animations

# TweenMax
```js
const tweenPromise = () => {
  return new Promise(resolve => {
    const el = document.getElementById('tween')

    TweenMax.fromTo(el, duration / 1000, {
      x: windowWidth
    }, {
      x: -elementWidth,
      ease: Power2.easeInOut,
      onComplete: () => resolve()
    })
  })
}
```
機能豊富なアニメーションライブラリ。38KB。
マグロを動かすためだけに使うようなライブラリではありません。
durationの指定が `ms` ではなくて `s` なので注意が必要です。

https://greensock.com/tweenmax

# Velocity.js
```js
const velocityPromise = () => {
  return new Promise(resolve => {
    const el = document.getElementById('velocity')

    Velocity(el, {translateX: windowWidth}, {duration: 0})
    Velocity(el, {translateX: -elementWidth}, {
      duration,
      easing: 'easeInOutCubic',
      complete: () => resolve()
    })
  })
}
```
jQueryのように書けるアニメーションライブラリ。16KB。

http://velocityjs.org

# anime.js
```js
const animePromise = () => {
  return new Promise(resolve => {
    const el = document.getElementById('anime')

    anime({
      targets: el,
      translateX: [windowWidth, -elementWidth],
      easing: 'easeInOutCubic',
      complete: () => resolve()
    })
  })
}
```
ファイルサイズも機能もシンプルなアニメーションライブラリ。5KB。
他のライブラリよりも後発なだけあって、必要最低限の機能を書きやすく作られています。

http://animejs.com

# 比較
<a href="https://codepen.io/noplan1989/full/yzeGVE" target="_blank"><img src="https://qiita-image-store.s3.amazonaws.com/0/89103/b6f4657f-e991-0ace-8af6-03c94f6c5fc4.gif" width="300px"></a>

https://codepen.io/noplan1989/full/yzeGVE

いつどこで、だれと見るかによって受ける印象が変わるのがマグロの面白さ。
もし今、anime.jsが気持ちよさそうに見えても、来月にはきっと感じ方が変わっているはずです。
正直なところ、大差ありません。（主観）

# まとめ
ブラウザ上をマグロに気持ちよく泳いでもらいたいという思いが傲慢であり、
アニメーションの選定という作業がそもそも不毛だったように思います。

マグロには海の中がよく似合う。

# ソース
https://codepen.io/noplan1989/pen/yzeGVE

# 参考
いらすとや
http://www.irasutoya.com/2016/04/blog-post_34.html