Web Animations APIを使った波紋アニメーション(ripple effect)

CSSアニメーションを調べていたらWeb Animations APIというJavaScriptで同じことができることがわかったので、勉強も兼ねて簡単なライブラリを作ってみました。この記事はその時得た知識のまとめです。

# Web Animations APIについて
CSSのAnimationsとTransitionsをJavaScriptで実現できるAPIです。
現在は、ChromeとFirefoxで実装が進められています。
Safari(WebKit)とEdgeについてはTodoには入ってますが、まだ実装されていません。
[web-animations-js](https://github.com/web-animations/web-animations-js)というpolyfillがあるので、そちらを使うとSafariやEdgeでも使えます。

# 成果物
今回作成したライブラリはパルス（波紋）のアニメーションです。
CSSアニメーションでも簡単に実装できます。これをJavaScriptでやります。
![pulsator.gif](https://qiita-image-store.s3.amazonaws.com/0/93166/e4a67af6-f7c6-a121-88b3-d81a485e9c95.gif)

[GitHub](https://github.com/shisama/Pulsator-JS)
[npm](https://www.npmjs.com/package/pulsator)
[Demoページ](https://shisama.github.io/Pulsator-JS/demo/)

<p data-height="354" data-theme-id="0" data-slug-hash="wyMabK" data-default-tab="js,result" data-user="shisama" data-embed-version="2" data-pen-title="Pulsator JS" class="codepen">See the Pen <a href="https://codepen.io/shisama/pen/wyMabK/">Pulsator JS</a> by Masashi Hirano (<a href="https://codepen.io/shisama">@shisama</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>

今回の記事はこのライブラリを作成した際に調べたことをまとめていきます。

# CSS
まずはCSSでの実装を紹介しておきます。

<p data-height="341" data-theme-id="0" data-slug-hash="oEppqr" data-default-tab="css,result" data-user="shisama" data-embed-version="2" data-pen-title="Pulsator CSS" class="codepen">See the Pen <a href="https://codepen.io/shisama/pen/oEppqr/">Pulsator CSS</a> by Masashi Hirano (<a href="https://codepen.io/shisama">@shisama</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>

keyframesで0pxから20pxの幅の波紋が広がるようにしています。
animationでkeyframesを1.5秒かけて実行。アニメーションを永続的に繰り返すためにinfiniteを設定しています。

# JavaScript
上記のCSSで実装しているアニメーションをJavaScriptのみで実装していきます。
順を追って説明します。

## styleの適用
今回はJavaScriptですべて完結したいため、アニメーション以外のstyleもJavaScriptにします。
-(ハイフン)で区切っていたCSSプロパティはキャメルケースに変更する必要があります。

```js
const el = document.querySelector(".pulsator");
const style = {
  width: "15px",
  height: "15px",
  borderRadius: "50%",
  borderColor: "red",
  background: "red",
  boxShadow: "0 0 0 rgba(255,0,0, 0.4)"
};
Object.assign(element.style, style);
```

## animate関数を使ってアニメーションを実行する
CSSのanimationやkeyframesをJavaScriptで実現するためには[animate関数](https://developer.mozilla.org/ja/docs/Web/API/Element/animate)を使います。

```js
const el = document.querySelector(".pulsator");
const animation = el.animate(
  {
    boxShadow: ["0 0 0 0 rgba(255,0,0, 1)", "0 0 0 20px rgba(255,0,0, 0)"]
  },
  {
    duration: 1500,
    iterations: Infinity
  }
);
```

animate関数を実行するとアニメーションは開始されます。
animate関数の第一引数にはkeyframes用にオブジェクトを設定します。
プロパティ名はキャメルケースにします。
fromとtoは配列で指定します。
複数のプロパティを設定したい場合は以下のようにオブジェクトのプロパティを追加するだけです。

```js
  {
    boxShadow: ["0 0 0 0 rgba(255,0,0, 1)", "0 0 0 20px rgba(255,0,0, 0)"],
    background: ["red", "pink"]
  },
```

また、以下のようにオブジェクトの配列として指定することも可能です。

```js
const animation = el.animate([
    {
      boxShadow: "0 0 0 0 rgba(255,0,0, 1)",
      background: "red"
    },
    {
      boxShadow: "0 0 0 20px rgba(255,0,0, 0)",
      background: "pink"
    }
  ],
  {
    duration: 1500,
    iterations: Infinity
  }
);
```

durationはミリ秒で指定します。これはCSSのanimationで1.5s指定していたkeyframesの実行時間です。
iterationsは繰り返しの方法を指定します。CSSのanimationでinfiniteと指定していたのをWeb Animations APIでは`Infinity`と指定します。

[KeyframeEffect](https://developer.mozilla.org/en-US/docs/Web/API/KeyframeEffect)を使用することでkeyframesの定義のみ先に行うことができます。

```js
const keyframes = new KeyframeEffect(
  el, 
  {
    boxShadow: ["0 0 0 0 rgba(255,0,0, 1)", "0 0 0 20px rgba(255,0,0, 0)"]
  },
  {
    duration: 1500,
    iterations: Infinity
  }
);

const animation = new Animation(keyframes, document.timeline);
```

## Animationのメソッドを使用してアニメーション操作を行う
animate関数はAnimationクラスのインスタンスを返却します。
定義したアニメーションを操作するためにはAnimationのメソッドを実行します。

### play()
アニメーションを開始します。

```js
animation.play()
```

例えば、あるボタンをクリックしたときに開始したいときは以下のようになります。

```js
document.querySelector(".some-button").addEventListener("click", function(event) {
  animation.play();
});
```

### cancel()
アニメーションをキャンセルします。

```js
animation.cancel()
```

例えば、あるボタンをクリックした際にアニメーションを開始し、5秒後に終了する場合は以下のようにします。

```js
document.querySelector(".some-button").addEventListener("click", function(event) {
  animation.play();
  setTimeout(function () {
    animation.cancel();
  }, 5000);
});
```

また[oncancel](https://developer.mozilla.org/en-US/docs/Web/API/Animation/oncancel)はキャンセル時に実行されるイベントハンドラです。キャンセル時に実行される関数を設定しておくことができます。

```js
animation.oncancel = function() {
  alert("canceled!");
}
```

### pause()
アニメーションを停止します。終了させるわけではありません。例えば今回の波紋のアニメーションであれば波紋が広がる途中でアニメーションが止まります。アニメーションの終了ではないため波紋は消えません。

```js
animation.pause()
```

例えばアニメーションしているDOMにマウスオーバーしたときにアニメーションを止めるには以下のようにします。

```js
el.onmouseover = function(event) {
  animation.pause();
};
```

### finish()
アニメーションを終了させます。
Animationクラスのプロパティである[playbackRate](https://developer.mozilla.org/en-US/docs/Web/API/Animation/playbackRate)に0または0より大きな値が設定されており、iterationsにInfinityを設定している場合はこの関数は使用できません。`InvalidState`というエラーが発生します。
※playbackRateは再生スピードを保持しているプロパティです。デフォルトでは1が設定されています。2を設定すれば2倍速になります。また、負の値を設定すると逆再生します。

```js
animation.finish()
```

例えば、ECSキーを押した時にアニメーションを終了する場合、以下のようになります。

```js
window.onkeydown = function(event) {
  if (event.key === "Escape") {
    animation.finish();
  }
};
```


また[onfinish](https://developer.mozilla.org/en-US/docs/Web/API/Animation/onfinish)はアニメーション終了時に実行されるイベントハンドラです。終了時に実行される関数を設定しておくことができます。

```js
animation.onfinish = function() {
  alert("finished!");
}
```

### reverse()
アニメーションを逆再生します。
playbackRateに`-1`を設定したときと同じ挙動をします。

```js
animation.reverse()
```

例えばEnterを押したときに逆再生するには以下のようになります。

```js
window.onkeydown = function(event) {
  if (event.key === "Enter") {
    animation.reverse();
  }
};
```

## Animationクラスのプロパティ
今回紹介したもの以外にも細かい設定を行うことができます。
多いので割愛させていただきます。もしくは別記事で使い方など書くかもしれません。
お手数おかけいたしますが、[MDN](https://developer.mozilla.org/en-US/docs/Web/API/Animation)を見て下さい。

## 

# まとめ
* JavaScriptだけでアニメーションを実装することができます。
* イベント発生時にアニメーションを操作することができます。
* まだ、ChromeとFirefoxでしか実装されていませんが、polyfill([web-animations-js](https://github.com/web-animations/web-animations-js))も存在します。
* w3cではまだDraftです。(https://www.w3.org/TR/web-animations-1/)

# 参考
- [Web Animations API - Web APIs | MDN](https://developer.mozilla.org/ja/docs/Web/API/Web_Animations_API)
- [Element.animate() - Web API インターフェイス | MDN](https://developer.mozilla.org/ja/docs/Web/API/Element/animate)
- [Animation - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/Animation)
- [Web Animations](https://www.w3.org/TR/web-animations-1/)

最後までお読みいただきありがとうございました。
不備、不明点はコメントにてお願い致します。