CSSアニメーションは楽しい

# 背景
業務にてHTMLとCSSをガッツリ触るタイミングがありました
久しぶりだったというのもありCSSアニメーションで遊びたいと思いました
CSSアニメーション遊んでいる中「こんなことが出来るんだ！」というものを少しでも広めらたらいいな、と思い記事を書く運びになりました

# コードについて
学生時代にCSSアニメーションの神・yui540さんを知り、色々とCSSアニメーションを真似ていました
社会人になってからCSSアニメーションにあまり触っていなかったので読み辛いコードになっているかもしれません
温かい心で見ていただければ幸いです

https://twitter.com/yui540

## CSSアニメーションについて

処理の種類として大まかに2つの処理があるかと思います

### transition
`:hover`や`:active`などの擬似クラスをトリガーとしてアニメーションを行う処理です
簡単に実装出来るため様々なサイトで見ます

### animation
擬似クラスではなく自分自身で処理を行うタイミングを決めてアニメーションを行えます
複雑なアニメーションを実装出来ます
本記事ではこちらを多用しています

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

## アニメーションの探し方
CSSアニメーションでどのようなものを作るかはYouTubeで
* ゲームのOP映像
* アニメのOP映像
* 曲のMV

などから探す良いかと思います

## 基本的な動作

2つのボックスにそれぞれのアニメーションを動かしていきます
・1つ目のボックス: 色の変化
・2つ目のボックス: 拡大

```html:html
<div class="example">
  <div class="animation__1"></div>
  <div class="animation__2"></div>
</div>
```

```scss:scss
.example {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 50px;
}

.animation__1 {
  position: relative;
  width: 100px;
  height: 100px;
  background: #f00;
  animation: animation__1 2s infinite alternate;
}

.animation__2 {
  width: 100px;
  height: 100px;
  background: #f00;
  animation: animation__2 2s infinite alternate;
  transform: scale(1, 1);
  transform-origin: top left;
}

@keyframes animation__1 {
  0% {
    background: #f00;
  }
  100% {
    background: #0f0;
  }
}

@keyframes animation__2 {
  0% {
    transform: scale(1, 1);
  }
  100% {
    transform: scale(2, 2);
  }
}
```

### 各プロパティの説明

#### `animation`
* `animation`: `animation__1 2s infinite alternate;`
* `animation-name`: 使用する`@keyframes`の名前（`animation__1`や`animation__2`）
* `animation-duration`: 2秒かけてアニメーションを実行
* `animation-iteration-count`: `infinite`で無限にループ
* `animation-direction`: `alternate`で順方向と逆方向を交互に繰り返す

#### `@keyframes`
`@keyframes`を使用してアニメーションの変化を定義します。

#### 背景色の変更（`animation__1`）

* `0%`: 背景色を赤 (`#f00`)
* `100%`: 背景色を緑 (`#0f0`)

#### 拡大アニメーション（`animation__2`）
* `0%`: `transform: scale(1, 1);` （元のサイズ）
* `100%`: `transform: scale(2, 2);` （2倍のサイズに拡大）

#### `transform`
* `transform: scale(1, 1);`：元のサイズ
* `transform: scale(2, 2);`：横・縦方向に2倍のサイズに拡大

#### `transform-origin`
* `transform-origin: top left;`：変形の基準点を左上に設定

![CleanShot 2025-02-02 at 19.53.04.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311195/511fca9d-cf9a-4e2b-6d0d-b1b8997a1441.gif)

https://css-animation-mocha.vercel.app/example

## 画面全体を動くアニメーション

複数のレイヤーが上下左右からスライドしながら表示される仕組みになっています。

```html:html
<div class="animation1">
  <div>
    <div></div>
    <div></div>
  </div>
  <div></div>
  <div></div>
  <div></div>
</div> 
```

```scss:scss
$start_animation: cubic-bezier(0.29, 0.92, 0.73, 1);
$end_animation: cubic-bezier(0.8, 0, 0.92, 0.19);

.animation1 {
  position: absolute;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}

.animation1 > div {
  position: absolute;
  top: 0;
  width: 100vw;
  height: 100vh;

  &:nth-child(1) {
    left: 0;
    display: flex;

    & > div {
      height: 100%;
      position: absolute;
      transform: translateY(-100%);
      animation: animation__1 2s forwards;
      animation-timing-function: $start_animation;

      &:nth-child(1) {
        z-index: 6;
        left: 0;
        width: 50%;
        background: #5b6a84;
        clip-path: polygon(100% 50%, 0% 100%, 0% 0%);
      }
      &:nth-child(2) {
        z-index: 1;
        right: 0;
        width: 100%;
        background: #5b6a84;
        clip-path: polygon(100% 0%, 50% 50%, 0% 0%);
      }
    }
  }

  &:nth-child(2) {
    z-index: 2;
    right: 0;
    background: #f28396;
    transform: translateY(-100%);
    animation: animation__2 2s forwards;
    animation-timing-function: $start_animation;
    clip-path: polygon(100% 0%, 100% 100%, 0% 0%);
  }

  &:nth-child(3) {
    z-index: 3;
    right: 0;
    transform: translateY(100%);
    background: linear-gradient(45deg, #596c85, #7086a9);
    animation: animation__3 2s forwards;
    animation-timing-function: $start_animation;
    clip-path: polygon(0% 100%, 100% 100%, 100% 0%);
  }

  &:nth-child(4) {
    z-index: 4;
    left: 0;
    background: linear-gradient(45deg, #f58c8f, #be6c6d);
    transform: translateY(100%);
    animation: animation__4 2s forwards;
    animation-timing-function: $start_animation;
    clip-path: polygon(0% 100%, 100% 100%, 0% 0%);
  }
}

@keyframes animation__1 {
  0% {
    transform: translateY(-100%);
  }
  50% {
    transform: translateY(0%);
    animation-timing-function: $end_animation;
  }
  65% {
    animation-timing-function: $end_animation;
    transform: translateY(-100%);
  }
  100% {
    animation-timing-function: $end_animation;
    transform: translateY(-100%);
  }
}

@keyframes animation__2 {
  0% {
    transform: translateY(-100%);
  }
  50% {
    transform: translateY(0%);
    animation-timing-function: $end_animation;
  }
  65% {
    transform: translateY(-100%);
    animation-timing-function: $end_animation;
  }
  100% {
    transform: translateY(-100%);
    animation-timing-function: $end_animation;
  }
}

@keyframes animation__3 {
  0% {
    transform: translateY(100%);
  }
  50% {
    transform: translateY(0%);
    animation-timing-function: $end_animation;
  }
  65% {
    transform: translateY(100%);
    animation-timing-function: $end_animation;
  }
  100% {
    transform: translateY(100%);
    animation-timing-function: $end_animation;
  }
}

@keyframes animation__4 {
  0% {
    transform: translateY(100%);
  }
  50% {
    transform: translateY(0%);
    animation-timing-function: $end_animation;
  }
  65% {
    transform: translateY(100%);
    animation-timing-function: $end_animation;
  }
  100% {
    transform: translateY(100%);
    animation-timing-function: $end_animation;
  }
}
```

最初に設定するだけでなくアニメーションの途中で任意のイージングに変更が可能です
下記のコードで言うと最初は`cubic-bezier(0.29, 0.92, 0.73, 1)`でアニメーションが行われ、50%以降は`cubic-bezier(0.8, 0, 0.92, 0.19)`でアニメーションが行われます

```scss:scss
$start_animation: cubic-bezier(0.29, 0.92, 0.73, 1);
$end_animation: cubic-bezier(0.8, 0, 0.92, 0.19);

&:nth-child(1) {
  left: 0;
  display: flex;

  & > div {
    height: 100%;
    position: absolute;
    transform: translateY(-100%);
    animation: animation__1 2s forwards;
    animation-timing-function: $start_animation;

    &:nth-child(1) {
      z-index: 6;
      left: 0;
      width: 50%;
      background: #5b6a84;
      clip-path: polygon(100% 50%, 0% 100%, 0% 0%);
    }
    &:nth-child(2) {
      z-index: 1;
      right: 0;
      width: 100%;
      background: #5b6a84;
      clip-path: polygon(100% 0%, 50% 50%, 0% 0%);
    }
  }
}

@keyframes animation__1 {
  0% {
    transform: translateY(-100%);
  }
  50% {
    transform: translateY(0%);
    animation-timing-function: $end_animation;
  }
  65% {
    animation-timing-function: $end_animation;
    transform: translateY(-100%);
  }
  100% {
    animation-timing-function: $end_animation;
    transform: translateY(-100%);
  }
}
```

![CleanShot 2025-02-02 at 19.56.28.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311195/713fd1b3-6883-5e27-f194-06e34aaf01f3.gif)


https://css-animation-mocha.vercel.app/animation1

## テキストへのアニメーション

テキストに対してアニメーションを行っていきます
予想ですがSVGで行ったほうが良いかも、と思っていますがまだSVGのアニメーションに慣れていないので使っておりません

```html:html
 <div class="animation2">
    <div class="text__wrap">
      <div class="text">テ</div>
      <div class="text">テ</div>
      <div class="text">キ</div>
      <div class="text">キ</div>
      <div class="text">ス</div>
      <div class="text">ス</div>
      <div class="text">ト</div>
      <div class="text">ト</div>
    </div>
</div>
```

```scss:scss
@mixin leftText($inDelay, $outDelay) {
  transform: translateY(-100%);
  -webkit-mask-image: linear-gradient(to right, black 50%, transparent 50%);
  mask-image: linear-gradient(to right, black 50%, transparent 50%);
  animation: inTop 0.2s ease-out $inDelay forwards,
    outTop 0.2s ease-in $outDelay forwards;
}

@mixin rightText($inDelay, $outDelay) {
  transform: translateY(100%);
  -webkit-mask-image: linear-gradient(to left, black 50%, transparent 50%);
  mask-image: linear-gradient(to left, black 50%, transparent 50%);
  animation: inDown 0.2s ease-out $inDelay forwards,
    outDown 0.2s ease-in $outDelay forwards;
}

@mixin rotate() {
  animation: rotate1 0.2s ease-in-out 0.2s forwards,
    rotate2 0.2s ease-in-out 0.6s forwards,
    rotate3 0.2s ease-in-out 1.1s forwards,
    rotate4 0.2s ease-in-out 1.6s forwards,
    rotate5 0.2s ease-in-out 2.1s forwards;
}

.animation3 {
  .text__wrap {
    position: relative;
    width: 10rem;
    height: 10rem;
    border: solid 0.5rem #aaa;
    background: #242424;
    overflow: hidden;
    z-index: 1;

    & > .text {
      top: 0;
      left: 0;
      position: absolute;
      font-size: 10rem;
      letter-spacing: 0;
      line-height: 1;
      font-weight: bold;

      &:nth-child(1) {
        @include leftText(0.2s, 0.6s);
      }
      &:nth-child(2) {
        @include rightText(0.2s, 0.6s);
      }

      &:nth-child(3) {
        @include leftText(0.7s, 1.1s);
      }
      &:nth-child(4) {
        @include rightText(0.7s, 1.1s);
      }

      &:nth-child(5) {
        @include leftText(1.2s, 1.6s);
      }
      &:nth-child(6) {
        @include rightText(1.2s, 1.6s);
      }
      &:nth-child(7) {
        @include leftText(1.7s, 2.1s);
      }
      &:nth-child(8) {
        @include rightText(1.7s, 2.1s);
      }
    }
  }

  & > .background {
    width: 200vw;
    height: 100vh;
    position: absolute;

    &:nth-child(3) {
      background: #222;
      top: -50%;
      left: -50%;
      transform-origin: center bottom;
      rotate: 0deg;
      @include rotate();
    }

    &:nth-child(2) {
      background: #444;
      top: 50%;
      left: -50%;
      transform-origin: center top;
      @include rotate();
    }
  }
}

@keyframes rotate1 {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(180deg);
  }
}
@keyframes rotate2 {
  0% {
    transform: rotate(180deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
@keyframes rotate3 {
  0% {
    transform: rotate(360deg);
  }
  100% {
    transform: rotate(540deg);
  }
}
@keyframes rotate4 {
  0% {
    transform: rotate(540deg);
  }
  100% {
    transform: rotate(720deg);
  }
}
@keyframes rotate5 {
  0% {
    transform: rotate(720deg);
  }
  100% {
    transform: rotate(900deg);
  }
}

@keyframes inTop {
  0% {
    transform: translateY(100%);
  }
  100% {
    transform: translateY(0);
  }
}

@keyframes outTop {
  0% {
    transform: translateY(0);
  }
  100% {
    transform: translateY(-100%);
  }
}

@keyframes inDown {
  0% {
    transform: translateY(-100%);
  }
  100% {
    transform: translateY(0);
  }
}

@keyframes outDown {
  0% {
    transform: translateY(0);
  }
  100% {
    transform: translateY(100%);
  }
}
```

異なったアニメーションを同時に設定することが出来ます
下記コードでいうと0.2秒後に`rotate1`のアニメーションが開始されて0.6秒後に`rotate2`のアニメーションが開始される...という具合になります
ただ注意しなければならないのが`animation-delay`を何も設定しないと同時に設定されている全てのアニメーションが実行されてしまい想定の動作をしなくなるので注意が必要です

```scss:scss
@mixin rotate() {
  animation: rotate1 0.2s ease-in-out 0.2s forwards,
    rotate2 0.2s ease-in-out 0.6s forwards,
    rotate3 0.2s ease-in-out 1.1s forwards,
    rotate4 0.2s ease-in-out 1.6s forwards,
    rotate5 0.2s ease-in-out 2.1s forwards;
}
```

![CleanShot 2025-02-02 at 19.55.24.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311195/d5fa8f51-adb9-16e9-bfa7-1e03e20b0ee0.gif)

https://css-animation-mocha.vercel.app/animation2

## 文字と模様を組み合わせたアニメーション

アニメーションとしては文字が出現されると同時に波紋のような模様が表示されるものになります

```html:html
<div class='animation2'>
  <div class="circle"></div>
  <div class="circle"></div>
  <div class="circle"></div>
  <div class="circle"></div>
  <div class='text'>
    <div>テ</div>
    <div>キ</div>
    <div>ス</div>
    <div>ト</div>
  </div>
</div>
```

```scss:scss
.animation2 {
  position: relative;

  & > .circle {
    scale: 0;
    border-width: 10px;

    &:nth-child(1) {
      position: absolute;
      top: -3rem;
      left: -3rem;
      width: 15rem;
      height: 15rem;
      border: solid 1rem #000;
      border-radius: 100%;
      transform-origin: center center;
      box-sizing: border-box;
      animation: scale 0.35s ease-in 0s forwards;
    }

    &:nth-child(2) {
      position: absolute;
      top: 5rem;
      left: 6rem;
      width: 15rem;
      height: 15rem;
      border: solid 1rem #000;
      border-radius: 100%;
      box-sizing: border-box;
      animation: scale 0.35s ease-in 0.15s forwards;
    }

    &:nth-child(3) {
      position: absolute;
      top: -2rem;
      right: 5rem;
      width: 15rem;
      height: 15rem;
      border: solid 1rem #000;
      border-radius: 100%;
      box-sizing: border-box;
      animation: scale 0.35s ease-in 0.35s forwards;
    }

    &:nth-child(4) {
      position: absolute;
      top: 3rem;
      right: -2rem;
      width: 15rem;
      height: 15rem;
      border: solid 1rem #000;
      border-radius: 100%;
      box-sizing: border-box;
      animation: scale 0.35s ease-in 0.55s forwards;
    }
  }
}

.text {
  font-size: 10rem;
  font-weight: bold;
  text-align: center;
  margin-top: 2rem;
  display: flex;
}
.text > div {
  opacity: 0;
  transform: scale(0.5);

  &:nth-child(1) {
    animation: pop-in 0.6s ease-in-out 0s forwards;
  }
  &:nth-child(2) {
    animation: pop-in 0.6s ease-in-out 0.15s forwards;
  }
  &:nth-child(3) {
    animation: pop-in 0.6s ease-in-out 0.35s forwards;
  }
  &:nth-child(4) {
    animation: pop-in 0.6s ease-in-out 0.55s forwards;
  }
}

@keyframes pop-in {
  0% {
    opacity: 0;
    transform: scale(0.5);
  }
  50% {
    opacity: 1;
    transform: scale(1.2);
  }
  80% {
    transform: scale(0.9);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes scale {
  0% {
    scale: 0;
    border-width: 10px;
  }
  50% {
    scale: 1;
    border-width: 10px;
  }
  100% {
    scale: 1;
    border-width: 0px;
  }
}
```
前述した2つのアニメーションよりもシンプルなコードにはなっています
自分の中では上位に好きなアニメーションです

![CleanShot 2025-02-02 at 19.58.46.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311195/3e71ae13-a175-a5b5-62ae-9dca78b69823.gif)

https://css-animation-mocha.vercel.app/animation3

# 参考

https://www.youtube.com/watch?v=UVtovmeUS_I