CSSアニメーションでぼう

< Back to WRITINGS

この記事はTentoアドベントカレンダー 22日目の記事です。

Tentoで講師をしている平田啓介です。またの名をHareと言います。

Webページでアニメーションを使いたいな〜〜と思い立ったので、試し書きしてみました。やってみたよ!というだけなので、詳しい説明や使ったテクニックの技術的な説明については、また今度記事にしようと思います。

自分のウェブページを忙しなく動かして閲覧者を驚かせましょう!

文字アニメーション

プレビュー右上のボタンで再ロード出来ます。

<div id="css-root">
  <div
    id="anim_hello-css"
    class="anim-item"
  >
    <div
      style:display="contents"
    >
      <div class="anim-item">H</div>
      <div class="anim-item">E</div>
      <div class="anim-item">L</div>
      <div class="anim-item">L</div>
      <div class="anim-item">O</div>
      <div class="anim-item">,</div>
      <div class="anim-item">C</div>
      <div class="anim-item">S</div>
      <div class="anim-item">S</div>
      <div class="anim-item">!</div>
    </div>
  </div>
</div>

<style>
  .anim-item {
    animation-duration: var(--duration);
    animation-timing-function: ease-in-out;
    animation-fill-mode: both;
    animation-delay: calc(var(--delay, 0s) + var(--delay-add, 0s));
  }

  #css-root {
    position: relative;
    color: #e5e7eb;
    font-size: 64px;
    font-weight: 800;
    width: 100%;
    height: 100%;
    overflow: hidden;
  }

  @keyframes panel-move-x {
    from {
      opacity: 0;
      transform: translate(-150%, -50%);
    }
    to {
      opacity: 1;
      transform: translate(-50%, -50%);
    }
  }
  @keyframes fade-in-bt {
    from {
      opacity: 0;
      transform: translateY(20px);
    }
    to {
      opacity: 1;
      transform: translateY(0px);
    }
  }
  #anim_hello-css {
    position: absolute;
    background-color: #808080;
    width: 100%;
    top: 50%;
    left: 50%;
    padding: 20px 0px;
    flex-direction: row;
    justify-content: center;
    gap: 2px;
    display: flex;
    animation-name: panel-move-x;
    animation-timing-function: ease-out;
    --delay: 0;
    --duration: 1s;
    > div {
      --delay: 1s;
      --duration: 0.2s;

      > div {
        animation-name: fade-in-bt;
        &:nth-child(1) {
          --delay-add: 0.1s;
        }
        &:nth-child(2) {
          --delay-add: 0.2s;
        }
        &:nth-child(3) {
          --delay-add: 0.3s;
        }
        &:nth-child(4) {
          --delay-add: 0.4s;
        }
        &:nth-child(5) {
          --delay-add: 0.5s;
        }
        &:nth-child(6) {
          --delay-add: 0.75s;
        }
        &:nth-child(7) {
          --delay-add: 0.9s;
        }
        &:nth-child(8) {
          --delay-add: 1.0s;
        }
        &:nth-child(9) {
          --delay-add: 1.1s;
        }
        &:nth-child(10) {
          --delay-add: 1.2s;
        }
      }
    }
  }
</style>

transition-delayに対して、transition-delay: var(--delay)のように変数を定義して、アニメーションの発火タイミングを制御します。

これを用いることで、そこそこ長いアニメーションも簡単に制御できます。

一文字づつ自動的に分割する仕組みなどは持ち合わせていないので、コピペと要素数分の:nth-childが並んでいますが、柔軟性は高い書き方だと思います。実際、単語ごとに少し間を開けたりして、より自然な動きにできている... かな?

ワイプアニメーション

<script>
  try {
    CSS.registerProperty({
      name: "--et",
      syntax: "<number>",
      inherits: true,
      initialValue: "0",
    });
    CSS.registerProperty({
      name: "--lt",
      syntax: "<number>",
      inherits: true,
      initialValue: "0",
    });
  } catch (e) {
    console.log(e);
  }
</script>

<div id="css-root">
  <div
    id="trans_circuler-1"
    class="anim-item"
  >
    <div class="anim-item"></div>
    <div class="anim-item"></div>
    <div class="anim-item"></div>
    <div class="anim-item"></div>
  </div>
</div>

<style>
  .anim-item {
    animation-duration: var(--duration);
    animation-timing-function: ease-in-out;
    animation-fill-mode: both;
    animation-delay: calc(var(--delay, 0s) + var(--delay-add, 0s));
  }

  #css-root {
    position: relative;
    color: #e5e7eb;
    font-size: 64px;
    font-weight: 800;
    width: 100%;
    height: 100%;
    overflow: hidden;
    background-color: #c0c0c0;
  }

  @keyframes tLiner {
    from {
      --lt: 0.0;
    }
    to {
      --lt: 1.0;
    }
  }

  @keyframes tEase {
    from {
      --et: 0.0;
    }
    to {
      --et: 1.0;
    }
  }

  @keyframes displaySwitch {
    from {
      display: block;
    }
    to {
      display: none;
    }
  }

  #trans_circuler-1 {
    position: absolute;
    top: 5%;
    left: 5%;
    width: 90%;
    height: 90%;
    display: block;

    --delay: 2.75s;
    --duration: 2s;

    border-radius: var(--radius-4xl);
    overflow: hidden;
    animation-name: displaySwitch, tEase;
    animation-timing-function: linear;

    transform-origin: center center;

    transform: translate(
      calc(800px * var(--et)),
      calc((-400px + var(--et) * 1600px) * var(--et))
    ) rotate(calc((1 - (1 - var(--et)) * (1 - var(--et))) * -180deg))
      scale(calc(1 - var(--et) * var(--et)));

    > div {
      width: 100%;
      height: 100%;
      position: absolute;
      animation-name: tEase, tLiner;
      animation-timing-function: ease-in-out, linear;
      --delay: 0.25s;
      --duration: 1s;
      &:nth-child(1) {
        --delay-add: 0s;
        background: conic-gradient(
          #808080 calc((var(--et) - (var(--lt) - var(--et)) * 0.25) * 100%),
          transparent 0
        );
      }
      &:nth-child(2) {
        --delay-add: 0.25s;
        background: conic-gradient(
          #707070 calc(var(--et) * 100%),
          transparent 0
        );
      }
      &:nth-child(3) {
        --delay-add: 0.5s;
        background: conic-gradient(
          #606060 calc((var(--et) - (var(--lt) - var(--et)) * -0.25) * 100%),
          transparent 0
        );
      }
      &:nth-child(4) {
        --delay-add: 0.75s;
        background: conic-gradient(
          #505050 calc((var(--et) - (var(--lt) - var(--et)) * -0.5) * 100%),
          transparent 0
        );
      }
    }
  }
</style>

くるっとするワイプと、外れて落ちるようなアニメーションを作ってみました。

conic-gradientを使った円運動のアニメーションで、--etなどの変数をもとに割合の変化で円運動を表現できます。delayでタイミングをずらしつつ、速度の緩急も少しずらしています。

また、外れて落ちるアニメーションは、線形に変化する--lt(linear timeの略としている)を用いて、重力による加速度と、簡単な水平速度を表現しています。回転も一定の角速度ではなく減衰する数式を用いています。

余談ですが、自作のコンポーネントプレビュー機構が直面したWeb標準上の課題により、CSSプロパティはregisterPropertyを使って定義する必要がありました。通常は@propertyで定義できます。

おわりに

アドカレのために映えるアニメーション作るぞ〜〜〜、と思っていたら、記事に乗せる分量と文章の割合を考えていなくて、CSSがやけに洗練されています。

この記事コに埋め込まれているコードをプレビューする仕組みも、実はこの記事の為に作りました。動的ビルドの話やShadowRootのCSSの仕様の穴など、これも色々あったので、また別で記事にしようと思います。

以上、CSSで遊んでみたでした〜。