1
0

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

Posted at

というわけでサンプル

See the Pen css / js / svg animation sample by juner clarinet (@juner) on CodePen.

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

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 でシーケンシャルに実行するようにして どうにかしています。

<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>
<style>
  :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);
    }
  }
  {
    // css click時
    let controller = new AbortController();
    const target = document.querySelector(".css .target.case-3");
    let promise = Promise.resolve();
    let i = 0;
    target.addEventListener('pointerup', (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 deferred = (() => {
          let resolve,reject;
          const promise = new Promise((res, rej) => [resolve, reject] = [res,rej]);
          return {resolve, reject, promise};
        })();

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

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

js

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

<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>
  :root {
    .js {
      --animate-color: red;
      .target {
        height: 100px;
        width: 100px;
        display:inline-block;
        background-color: green;
      }
      .animate {
        background: var(--animate-color);
      }
    }
  }
  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 の指定が無理なのが難点……。

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
:root {
  .svg {
    --animate-color: red;
    .target {
      height: 100px;
      width: 100px;
      display:inline-block;
      background-color: green;
    }
    svg {
      width: 100px;
      height: 100px;
      overflow: visible;
    }
  }
}

以上。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0