css / js / svg に於ける それぞれの アニメーションサンプルを作成してみた

# というわけでサンプル

<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="ZENezxL" data-pen-title="css / js / svg animation sample" data-user="juner" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/juner/pen/ZENezxL">
  css / js / svg animation sample</a> by juner clarinet (<a href="https://codepen.io/juner">@juner</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## で具体的な違いについて の概要

| | css | js | svg |
| - | - | - | -
| 表示時 | css | js | svg 
| hover時 | css | js | svg
| click時 | css + js | js | svg

どれも css で装飾していますが、ここで言っている違いで指しているのはアニメーションに関してだけです。 js でトリガーが必要なら js って書いてます。

## 各種ソースと解説

### css
css は簡単で keyframe 作って 表示時 / hover時に於いててはそれを特定タイミングで適用するだけです。

ただ、 click 時については css だけでは無理です。

js 側は イベントベースとなる為、 Promise でシーケンシャルに実行するようにして どうにかしています。

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

```html
<div class="css">
  <h1>css</h1>
  <h2>表示時</h2>
  <div class="target case-1"></div>
  <h2>hover時</h2>
  <div class="target case-2"></div>
  <h2>click時</h2>
  <div class="target case-3"></div>
</div>
```
```css
:root {
  .css {
    --animate-color: red;
    .target {
      height: 100px;
      width: 100px;
      display:inline-block;
      background-color: green;
    }
    /* css 常時 */
    .case-1 {
      background: var(--animate-color);
      animation: linear 1000ms infinite normal rotate;
    }
    /* css hover時 */
    .case-2:hover {
      background: var(--animate-color);
      animation: linear 1000ms infinite normal rotate;
    }
    .case-3.animate {
      background: var(--animate-color);
      animation: linear 1000ms 1 normal rotate;
    }
  }
}
@keyframes rotate {
  from {
    transform: rotate(0);
  }

  to {
    transform: rotate(1turn);
  }
}
```
```js
{
  // css click時
  let controller = new AbortController();
  const target = document.querySelector(".css .target.case-3");
  let promise = Promise.resolve();
  let i = 0;
  target.addEventListener("click", (e) => {
    const target = e.currentTarget;

    // 前をキャンセル
    controller.abort();
    const c = new AbortController();
    controller = c;

    // シーケンシャルに実行しないと タイミングに問題が出る
    promise = promise.then(async () => {
      target.classList.add("animate");
      const signal = c.signal;

      // キャンセル時は class を外すだけ
      signal.addEventListener("abort", () => {
        target.classList.remove("animate");
      });

      const {resolve, promise} = Promise.withResolvers();

      // animationend / animationcancel どちらかが発生するまで待機
      target.addEventListener("animationend", resolve);
      target.addEventListener("animationcancel", resolve);
      await promise;

      // キャンセルしていなければ キャンセル
      if (!signal.aborted) c.abort();
      // イベントの解放
      target.removeEventListener("animationend", resolve);
      target.removeEventListener("animationcancel", resolve);
    });
  });
}
```

### js

js では Web Animation API を利用して 比較的簡単に実行/キャンセルができ、待つのも Promise で簡単です。

https://developer.mozilla.org/ja/docs/Web/API/Web_Animations_API

```html
<div class="js">
  <h1>js</h1>
  <h2>表示時</h2>
  <div class="target case-1"></div>
  <h2>hover時</h2>
  <div class="target case-2"></div>
  <h2>click時</h2>
  <div class="target case-3"></div>
</div>
```
```css
:root {
  .js {
    --animate-color: red;
    .target {
      height: 100px;
      width: 100px;
      display:inline-block;
      background-color: green;
    }
    .animate {
      background: var(--animate-color);
    }
  }
}
```
```js
const keyframes = [
  { transform: 'rotate(0)'},
  { transform: 'rotate(1turn)'},
];
const options = {
  duration: 1000,
  iterations: Infinity,
};
const onetime = {
  duration: 1000,
  iterations: 1,
};
{
  // js 表示時
  const target = document.querySelector(".js .target.case-1");
  target.classList.add('animate');
  target.animate(keyframes, options);
}
{
  // js hover時
  let controller = new AbortController();
  const target = document.querySelector(".js .target.case-2");
  target.addEventListener('pointerover', (e) => {
    const target = e.currentTarget;
    target.setPointerCapture(e.pointerId);
    target.classList.add('animate');
    const animate = target.animate(keyframes, options);
    const signal = controller.signal;
    signal.addEventListener('abort', () => {
      target.classList.remove('animate');
      animate.cancel();
    })
  });
  target.addEventListener('pointerout', (e) => {
    controller.abort();
    controller = new AbortController();
  });
}
{
  // js click時
  let controller = new AbortController();
  const target = document.querySelector(".js .target.case-3");
  target.addEventListener('pointerup', async (e) => {
    const target = e.currentTarget;

    // 前をキャンセル
    controller.abort();
    const c = new AbortController();
    controller = c;

    const animate = target.animate(keyframes, onetime);
    const signal = c.signal;
    // キャンセルすると状態をクリアする
    signal.addEventListener('abort', () => {
      target.classList.remove('animate');
      if (animate.playState === "finished") return;
      animate.cancel();
    });
    target.classList.add('animate');
    try {
      await animate.finished;
    }catch(e){ }
      // 完了したので状態をクリアする
      c.abort();
  });
}
```

### svg

svg はなんとcssとは違いクリックしたときという表現がある為、全部svgでできてしまいます。
ただ、 animate要素等で css custom property の指定が無理なのが難点……。

https://developer.mozilla.org/ja/docs/Web/SVG/Element/animateTransform

```html:html
<div class="svg">
  <h1>svg</h1>
  <h2>表示時</h2>
  <svg title="表示時">
    <rect x="0" y="0" height="100" width="100" class="target case-1" id="svg-target-case-1" >
      <animateTransform xlink:href="#svg-target-case-1"
        attributeName="transform"
        type="rotate"
        from="0 50 50"
        to="360 50 50"
        dur="1s"
        repeatCount="indefinite"
      />
      <animate xlink:href="#svg-target-case-1"
        attributeName="fill"
        values="red"
        dur="1s"
        repeatCount="indefinite" />
    </rect>
  </svg>
  <h2>hover時</h2>
  <svg title="hover時">
    <rect x="0" y="0" height="100" width="100" class="target case-2" id="svg-target-case-2" fill="green">
      <animateTransform xlink:href="#svg-target-case-2"
        attributeName="transform"
        type="rotate"
        from="0 50 50"
        to="360 50 50"
        dur="1s"
        repeatCount="indefinite"
        begin="mouseenter"
        end="mouseout" />
      <animate xlink:href="#svg-target-case-2"
        attributeName="fill"
        values="red"
        dur="1s"
        repeatCount="indefinite"
        begin="mouseenter"
        end="mouseout" />
    </rect>
  </svg>
  <h2>click時</h2>
  <svg title="hover時">
    <rect x="0" y="0" height="100" width="100" class="target case-3" id="svg-target-case-3" fill="green">
      <animateTransform xlink:href="#svg-target-case-3"
        attributeName="transform"
        type="rotate"
        from="0 50 50"
        to="360 50 50"
        dur="1s"
        repeatCount="1"
        begin="click" />
      <animate xlink:href="#svg-target-case-3"
        attributeName="fill"
        values="red"
        dur="1s"
        repeatCount="1"
        begin="click" />
    </rect>
  </svg>
</div>
```
```css:css
:root {
  .svg {
    --animate-color: red;
    .target {
      height: 100px;
      width: 100px;
      display:inline-block;
      background-color: green;
    }
    svg {
      width: 100px;
      height: 100px;
      overflow: visible;
    }
  }
}
```

以上。