Web Animations API を使ってみる

Animation周りが苦手だったので、Flash のタイムラインアニメーションエディタみたいなものを練習がてら作ってみたい、ということで勉強した。

本記事は勉強ログ気味。

## Web Animations API とは

CSS Animation の keyframe 制御を JS から可能にしたようなもの。
CSS Animation は 自分で止まったり再開したりできないし、フレーム制御も出来ない。 JS から制御できないのでコントロールしづらかったが、その問題を解決する。

## Web Animations 実装具合と Polyfill

Can I Use 見る限りは Firefox で 実装済み、 Chrome で実装中
https://caniuse.com/#feat=web-animation

polyfill なしで試した結果、chrome で `elem.animate(...)` は動くけど、 `elem.getAnimations()` や `document.timeline` が存在しない感じだった。


実際は polyfill 使うことになると思う。

https://github.com/web-animations/web-animations-js/tree/866e01726cf61ffeabad2baa7f6c10e3cadee138


## 簡単な使い方

polyfill を npm から適用

```
import 'web-animations-js/web-animations-next.min.js'
```

使う

```js
const el = document.querySelector('.my-selector')
el.animate([{opacity: 0}, {opacity: 1}], 1000)
```

1000ms かけてopacity: 0 から 1 まで遷移。デフォルトだとリピート無し。

無限リピート

```js
const el = document.querySelector('.my-selector')
el.animate([{opacity: 0}, {opacity: 1}], {
  duration: 1000,
  iterations: Infinity
})
```

iterations で指定した回数繰り返す。


W3C の Spec 見る限りは次のコードも動くはずだが、 ポリフィルを使う限りは動かなかった。


```js
const el = document.querySelector('.my-selector')
el.animate({opacity: 0}, 1000)
```

https://www.w3.org/TR/web-animations-1/#updating-the-finished-state

## Stop & Go

```js
const animation = el.animate([{opacity: 0}, {opacity: 1}], 1000)
animation.pause()
animation.play()
```

あるエレメントに適用されている全ての animation を操作する。

```
el.getAnimations().forEach(a => a.pause())
el.getAnimations().forEach(a => a.play())
```

ドキュメント内の全てのアニメーションを取得する。

```
const anims = document.timeline.getAnimations()
```

## keyframe の offset 制御

```js
el.animate(
  [
    {opacity: 0, offset: 0},
    {opacity: 0.5, offset: 0.8},
    {opacity: 1, offset: 1}
  ],
  2000
)
```

0 から 始まり、 1 で終わる。 (0と1は省略可能)
この例では 2000 * 0.8 = 1600 ms かけて opacity: 0.5 になり、残り 400ms で 1になる。

## easing 制御

```js
el.animate(
  [
    {opacity: 0, offset: 0},
    {opacity: 0.5, offset: 0.8, easing: 'ease-in-out'},
    {opacity: 1, offset: 1}
  ],
  2000
)
```

何も指定しない場合、 デフォルトの easing は linear。 ここで指定できるのは timing-function の文字列

https://developer.mozilla.org/ja/docs/Web/CSS/timing-function

## CSS timing function

ある時間中に 0 から 1 まで値が変化(transisiton)する時、それがどのような変化を描くかの関数、を示す css 上での表現
組み込みの timinig-function は linear, ease, ease-in, ease-out, ease-in-out。

それらは 3次ベジェ曲線 cubic-bezier(x1, y1, x2, y2) で表現可能。

https://en.wikipedia.org/wiki/Bézier_curve#Cubic_B.C3.A9zier_curves

離散的ではない値の表現には steps, steps-start, step-end を使う。


## React で使う

自分ならこういう風に表現するかなぁという実装

```js
import 'web-animations-js/web-animations-next.min.js'
import React from 'react'
import ReactDOM from 'react-dom'

class Animation extends React.Component {
  componentDidMount() {
    const el = ReactDOM.findDOMNode(this)
    const { timelines } = this.props
    for (const tl of timelines) {
      const { keyframes, ...others } = tl
      el.animate(tl.keyframes, others)
    }
  }

  render() {
    return this.props.children
  }
}

export default () => {
  return (
    <Animation
      timelines={[
        {
          keyframes: [{ opacity: 1 }, { opacity: 0.5 }, { opacity: 1 }],
          duration: 1000,
          iterations: Infinity
        },
        {
          keyframes: [
            { background: 'white', offset: 0 },
            { background: 'red', offset: 0.9 },
            { background: 'white', offset: 1 }
          ],
          duration: 3000,
          spacing: 'spaced(left)'
        }
      ]}
    >
      <span>Hello</span>
    </Animation>
  )
}
```


## 不満

組み込みの easing 関数の任意フレームでの値を JS から知る方法がない。 getComputedProperty() を使うしかない。

## 次に調べること

普通に使うだけならこれだけで一通りなんでも出来る。
タイムラインアニメーションエディタを作る場合、要はこの keyframes の JSON を生成できれば良い。
任意の実行フレームに巻き戻したり、とかやるとややこしくなってくる。それはこれから調べる。

実行効率のために composition に関する部分をちゃんと読まないといけなさそう。

## 参考

https://www.w3.org/TR/web-animations-1/

これを上から下まで全部読むだけ