JavaScript
Web-Animations

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'

使う

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

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

無限リピート

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

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

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

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

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 制御

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 制御

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 で使う

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

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/

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