CSS @keyframes のタイムライン制御テクニック集

2026.04.24 09:00
2026.03.16 13:33
CSS @keyframes のタイムライン制御テクニック集

CSSアニメーションって、@keyframesfromto を書くだけだと思っていませんか?実は @keyframes にはタイムラインを細かく制御するテクニックがたくさんあるんですよね。今回は、知っておくと表現の幅がグッと広がるテクニックをまとめてみました。

サンプルページを見る(パーセンテージ指定の「待ち」、alternate/reverseの違い、負のdelay、1文字ずつ表示を実際に試せます)

from/toだけじゃない!パーセンテージ指定の活用

@keyframes では from(= 0%)と to(= 100%)だけでなく、パーセンテージで細かくタイミングを指定できます。

@keyframes slide-bounce {
  0% {
    transform: translateX(0);
  }
  60% {
    transform: translateX(200px);
  }
  80% {
    transform: translateX(180px);
  }
  100% {
    transform: translateX(200px);
  }
}

この例だと、全体の60%の時間で右に200px移動して、そこから少し戻って、最後にまた200pxの位置に落ち着くバウンド効果を作っています。from/to だけでは作れない動きですね。

同じ値を複数のキーフレームで指定して「待ち」を作る

アニメーションの途中で「しばらく止まっている」状態を作りたいことってありますよね。そんなときは、同じ値を複数のキーフレームに指定するだけでOKです。

@keyframes fade-with-pause {
  0% {
    opacity: 0;
  }
  30% {
    opacity: 1;
  }
  70% {
    opacity: 1; /* 30%〜70%の間は opacity: 1 のまま停止 */
  }
  100% {
    opacity: 0;
  }
}

30%〜70%の間は opacity: 1 のまま変化しないので、表示された状態でしばらく「待つ」動きになります。カンマ区切りで 30%, 70% { opacity: 1; } のようにまとめて書くこともできますね。

animation-direction: alternate / reverse の使い分け

animation-direction を使うと、アニメーションの再生方向を変えられます。

  • normal: 0% → 100% の順で再生(デフォルト)
  • reverse: 100% → 0% の逆順で再生
  • alternate: 奇数回は正順、偶数回は逆順で再生
  • alternate-reverse: 奇数回は逆順、偶数回は正順で再生
/* 行って戻ってを繰り返す */
.bounce {
  animation: move 1s ease-in-out infinite alternate;
}

@keyframes move {
  from {
    transform: translateY(0);
  }
  to {
    transform: translateY(-20px);
  }
}

alternate を使えば、@keyframes 側で戻りのモーションを書かなくても往復アニメーションが作れます。コード量が減ってスッキリしますね。

animation-fill-mode: forwards vs both の違い

animation-fill-mode は、アニメーションの再生前後にどのスタイルを適用するかを決めるプロパティです。ここが地味にハマりやすいポイントなんですよね。

  • none(デフォルト): アニメーション前後ともキーフレームのスタイルは適用しない
  • forwards: アニメーション終了後、最後のキーフレームのスタイルを維持する
  • backwards: アニメーション開始前(delay中)に最初のキーフレームのスタイルを適用する
  • both: forwardsbackwards の両方を適用する
/* forwards: 終了後に最後の状態を維持 */
.fade-in-forwards {
  opacity: 0;
  animation: fadeIn 1s ease forwards;
}

/* both: delay中も最初のキーフレームを適用 + 終了後も維持 */
.fade-in-both {
  animation: fadeIn 1s ease 2s both;
}

@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

animation-delay を使っている場合、forwards だけだと delay 中は要素のデフォルトスタイルが表示されてしまいます。both にすれば delay 中も from のスタイル(この場合 opacity: 0)が適用されるので、チラつきを防げます。

animation-play-state: paused でホバー制御

animation-play-state を使うと、アニメーションの一時停止・再開ができます。ホバーでアニメーションを制御するUIに使えますね。

/* 普段は停止、ホバーで再生 */
.hover-animate {
  animation: rotate 2s linear infinite paused;
}

.hover-animate:hover {
  animation-play-state: running;
}

@keyframes rotate {
  to {
    transform: rotate(360deg);
  }
}

逆に、普段は動いていてホバーで止めるパターンも作れます。マウスを載せると止まるマーキーのような表現にも使ってみました。

/* 普段は再生、ホバーで停止 */
.marquee {
  animation: scroll 10s linear infinite;
}

.marquee:hover {
  animation-play-state: paused;
}

animation-delay にマイナス値を使う(途中から開始)

animation-delay にマイナス値を指定すると、アニメーションを途中の状態からスタートさせることができます。これ、意外と知らない人が多いんですよね。

.element {
  animation: spin 4s linear infinite;
  animation-delay: -2s; /* 2秒分進んだ状態(180度回転した状態)からスタート */
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

-2s を指定すると、4秒のアニメーションのうち2秒分が既に経過した状態(つまり50%の位置 = 180度回転)から始まります。ページ読み込み時に「最初から動いている」ように見せたいときに便利です。

複数アニメーションのカンマ区切り指定

1つの要素に複数のアニメーションを同時に適用したい場合は、カンマ区切りで指定できます。

.multi-animation {
  animation:
    fadeIn 1s ease forwards,
    slideUp 1s ease forwards;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes slideUp {
  from { transform: translateY(20px); }
  to { transform: translateY(0); }
}

フェードインとスライドアップを同時に実行する、といった動きが1行で書けます。

ただし注意点があります。同じCSSプロパティ(例えば transform)を複数のアニメーションで操作すると、後に指定したアニメーションで上書きされてしまいます。transform を使うアニメーションを複合したい場合は、1つの @keyframes にまとめるか、個別の変形プロパティ(translate, rotate, scale)を使い分けると良いですね。

実例: ローディングドットの時間差アニメーション

ここからは実例を見ていきましょう。まずは、よく見かけるローディングドットのアニメーションです。3つのドットが時間差で拡縮する動きを作ってみました。

<div class="loading-dots">
  <span class="dot"></span>
  <span class="dot"></span>
  <span class="dot"></span>
</div>
.loading-dots {
  display: flex;
  gap: 8px;
  align-items: center;
}

.dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background-color: #3498db;
  animation: bounce-dot 1.4s ease-in-out infinite both;
}

.dot:nth-child(1) {
  animation-delay: -0.32s;
}

.dot:nth-child(2) {
  animation-delay: -0.16s;
}

.dot:nth-child(3) {
  animation-delay: 0s;
}

@keyframes bounce-dot {
  0%, 80%, 100% {
    transform: scale(0.6);
    opacity: 0.5;
  }
  40% {
    transform: scale(1);
    opacity: 1;
  }
}

ポイントは、animation-delay にマイナス値を使っているところです。こうすることで、ページ読み込み直後から3つのドットがバラバラのタイミングで動いている状態になります。プラス値だと最初しばらくは全部止まっていて、順番に動き始めてしまうんですよね。

また、0%, 80%, 100% のように複数のキーフレームに同じ値を指定して「待ち」を作っているのもポイントです。40%の時点でスケールが最大になり、その後はしばらく小さいままになるので、波のような動きが生まれます。

実例: テキストの1文字ずつ表示

次は、テキストが1文字ずつフェードインしながら表示されるアニメーションです。HTMLでは各文字を span で囲んで、CSSカスタムプロパティでインデックスを持たせます。

<div class="text-reveal">
  <span style="--i: 0">H</span>
  <span style="--i: 1">e</span>
  <span style="--i: 2">l</span>
  <span style="--i: 3">l</span>
  <span style="--i: 4">o</span>
  <span style="--i: 5">,</span>
  <span style="--i: 6">&nbsp;</span>
  <span style="--i: 7">W</span>
  <span style="--i: 8">o</span>
  <span style="--i: 9">r</span>
  <span style="--i: 10">l</span>
  <span style="--i: 11">d</span>
  <span style="--i: 12">!</span>
</div>
.text-reveal span {
  display: inline-block;
  opacity: 0;
  transform: translateY(10px);
  animation: char-appear 0.4s ease forwards;
  animation-delay: calc(var(--i) * 0.08s);
}

@keyframes char-appear {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

CSSカスタムプロパティ --i で各文字のインデックスを持たせて、calc()animation-delay を動的に計算しています。0.08秒ずつズラすことで、1文字ずつ順番に表示される効果になります。

animation-fill-mode: forwards を指定しているので、アニメーション終了後も opacity: 1 の状態が維持されます。これがないと、アニメーション後に文字が消えてしまうので注意ですね。

まとめ

今回紹介したテクニックをまとめるとこんな感じです。

  • パーセンテージ指定: from/to だけでなく、細かいタイミングで動きを制御
  • 同じ値の繰り返し: アニメーションの途中で「待ち」を作る
  • animation-direction: alternate で往復、reverse で逆再生
  • animation-fill-mode: forwards で終了状態を維持、both で delay 中も適用
  • animation-play-state: paused / running でホバー制御
  • マイナス delay: アニメーションを途中から開始させる
  • カンマ区切り: 1つの要素に複数アニメーションを適用

どれもシンプルなテクニックですが、組み合わせることでCSSだけでも複雑なアニメーションが作れるようになります。JavaScriptに頼らずにリッチな動きを実現できるので、パフォーマンス面でもメリットがありますね。

サンプルページで実際の動きを確認する

今回は以上です!