ローディング中にくるくる回るアイコンや、ページを開いた瞬間にふわっと現れる文字。こうした「きっかけなしで自律的に動き続けるアニメーション」は、CSS の @keyframes と animation プロパティで作れます。この記事では、@keyframes でアニメーションの動きを定義し、animation で要素に適用する基本から、各サブプロパティの役割、繰り返しや往復の指定、そして「動かない・1 回で止まる」というつまずきの原因までを、初心者の方にも分かるように解説します。回転スピナーやフェードインの動くデモも用意しました。
目次
@keyframes と animation でできること
CSS でアニメーションを付ける方法は大きく 2 つあります。1 つは transition、もう 1 つがこの記事で扱う @keyframes と animation です。@keyframes は「アニメーションの途中経過(キーフレーム)」を定義するルールで、animation はそのキーフレームを要素に当てはめて再生するためのプロパティです。役割が分かれているのがポイントで、まず動きの中身を @keyframes で作り、それを animation で呼び出して使います。
これにより、ホバーやクリックといったきっかけがなくても、ページを表示しただけで要素を動かしたり、同じ動きを何度も繰り返したりできます。回転し続けるローディングスピナー、点滅するアイコン、ゆっくり拡大と縮小を繰り返すボタンなどは、すべて @keyframes と animation の組み合わせで実現できます。
transition との違い
混同しやすいのが transition との違いです。transition は、要素のプロパティが ある値から別の値へ変わるとき、その変化をなめらかにつなぐものです。そのため :hover やクラスの付け外しといった「値を変えるきっかけ」が必ず必要で、基本は始まりと終わりの 2 状態を行き来するだけです。
一方 animation は、@keyframes に書いた途中経過に沿って 自律的に動きます。ページを開いた瞬間に再生を始められ、回数を指定して繰り返したり無限ループさせたりできます。さらに、中間地点を複数定義して複雑な動きを作れるのも transition にはない特徴です。ざっくり言えば、ホバーで色をふわっと変える程度なら transition、回り続ける・点滅し続けるといった繰り返しの動きなら animation が向いています。transition 自体の詳しい使い方は別記事で解説しているので、ここでは違いの整理に留めます。
@keyframes の書き方
@keyframes は、アニメーションの名前と、再生の各時点でのスタイルを定義します。もっとも単純なのは、開始時点を from、終了時点を to で書く形です。次の例は、要素を 0 度から 360 度まで回転させる動きを spin という名前で定義しています。
/* spin という名前でアニメーションを定義 */
@keyframes spin {
from {
transform: rotate(0deg); /* 開始時点の状態 */
}
to {
transform: rotate(360deg); /* 終了時点の状態 */
}
}
from は再生の 0%、to は 100% を表します。途中の動きをもっと細かく制御したいときは、from / to の代わりに 0% から 100% までのパーセンテージで、いくつでも中間地点を指定できます。次の例は、半分の地点で透明度を下げて点滅させる blink です。
@keyframes blink {
0% {
opacity: 1;
}
50% {
opacity: 0.2; /* 中間地点でいったん薄くする */
}
100% {
opacity: 1;
}
}
/* 同じスタイルになる時点はカンマでまとめられる */
@keyframes pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
}
同じスタイルを複数の時点で指定したいときは、0%, 100% のようにカンマで区切ってまとめられます。pulse の例では、開始と終了で元の大きさ、中間で 1.2 倍に拡大することで、ふくらんでから戻る動きを表現しています。
animation で要素に適用する
定義した @keyframes は、それだけでは何も起きません。動かしたい要素に animation プロパティを指定して、はじめて再生されます。最小の例として、先ほどの spin を 1 秒・等速・無限回で適用してみます。
.spinner {
width: 40px;
height: 40px;
border: 5px solid #dbeafe;
border-top-color: #3b82f6; /* 一辺だけ色を変えると回転が見える */
border-radius: 50%;
/* spin を 1 秒かけて・等速で・無限に繰り返す */
animation: spin 1s linear infinite;
}
これだけで、要素がくるくると回り続けるローディングスピナーになります。animation: spin 1s linear infinite; の部分が一括指定で、それぞれの値が何を意味するのかは次の節で整理します。
animation のサブプロパティ
animation は、いくつかの個別プロパティをまとめた一括指定(ショートハンド)です。それぞれを個別に書くこともできます。まずは各プロパティの役割と代表的な値を整理しておきましょう。
| プロパティ | 役割と主な値 |
|---|---|
animation-name | 適用する @keyframes の名前。spin など定義した名前を指定する。 |
animation-duration | 1 回の再生にかける時間。1s や 500ms のように秒・ミリ秒で指定する。 |
animation-timing-function | 時間内での速さの変化(イージング)。ease / linear / ease-in-out など。 |
animation-delay | 再生が始まるまでの待ち時間。0.3s なら 0.3 秒待ってから動き出す。 |
animation-iteration-count | 繰り返す回数。1(初期値)や 3 などの数値、無限に繰り返すなら infinite。 |
animation-direction | 再生方向。normal(毎回同じ向き)/ reverse(逆再生)/ alternate(往復)/ alternate-reverse。 |
animation-fill-mode | 再生前後にどの状態を保つか。none(初期値)/ forwards(終了状態を保持)/ backwards / both。 |
animation-play-state | 再生中か一時停止か。running(初期値)/ paused。ホバーで止めるなどに使う。 |
とくに繰り返しの動きで重要なのが animation-iteration-count と animation-direction です。infinite を指定すればスピナーのように止まらず動き続け、alternate を指定すると往路(0% → 100%)と復路(100% → 0%)を交互に再生するため、行って戻ってを自然に繰り返せます。また animation-fill-mode: forwards は、繰り返しのないアニメーションで「終わった状態のまま止めたい」ときに使います。これを指定しないと、再生が終わった瞬間に最初の状態へ戻ってしまいます。
一括指定(ショートハンド)の書き方
実際の制作では、animation プロパティ 1 行でまとめて書くのが一般的です。値はスペース区切りで並べ、順番はおおむね自由ですが、時間に関する 2 つの値だけは順番が意味を持ちます。先に書いた時間が duration、後に書いた時間が delay と解釈されるため、遅延を付けたいときは時間を 2 つ並べます。
/* name duration timing-function delay iteration-count direction の順で書いた例 */
.spinner {
animation: spin 1s linear 0s infinite normal;
}
/* よく使う最小構成:名前・時間・繰り返し回数だけ */
.dot {
animation: blink 1.2s infinite;
}
/* 往復させたいときは alternate を加える */
.pulse {
animation: pulse 2s ease-in-out infinite alternate;
}
/* 1 回だけ再生し、終了状態を保持したいとき */
.fade {
animation: fade-in 0.6s ease-out forwards;
}
個別プロパティに分けて書くと、対応関係がはっきりします。読みやすさや、特定の値だけを上書きしたいときには個別指定が便利です。
.spinner {
animation-name: spin; /* どの @keyframes を */
animation-duration: 1s; /* 1 回に何秒かけて */
animation-timing-function: linear; /* どんな速さの変化で */
animation-iteration-count: infinite; /* 何回繰り返すか */
}
動く例で確認する
言葉だけでは伝わりにくいので、実際に動くデモを用意しました。下のプレビューには 3 つの要素があります。左は infinite で回り続けるローディングスピナー、真ん中は alternate でふわっと現れては戻るラベル、右は 0%〜100% の複数キーフレームで点滅する丸です。CSS タブを開くと、それぞれの @keyframes と animation の指定を確認できます。
<div class="demo">
<div class="spinner"></div>
<div class="fade">ふわっと表示</div>
<div class="dot"></div>
</div>
.demo {
display: flex;
align-items: center;
justify-content: center;
gap: 40px;
flex-wrap: wrap;
padding: 48px;
font-family: sans-serif;
}
/* 1. ローディングスピナー:infinite で回転を繰り返す */
.spinner {
width: 48px;
height: 48px;
border: 5px solid #dbeafe;
border-top-color: #3b82f6; /* 一辺だけ色を変えると回転が分かる */
border-radius: 50%;
/* spin を 1 秒・等速・無限回 */
animation: spin 1s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* 2. ふわっと表示:fade-in を alternate で行き来させる */
.fade {
padding: 12px 20px;
color: #fff;
background-color: #10b981;
border-radius: 8px;
/* 1.5 秒かけて、戻りながら無限に繰り返す */
animation: fade-in 1.5s ease-in-out infinite alternate;
}
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(12px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 3. 点滅:0%〜100% で複数のキーフレームを指定 */
.dot {
width: 40px;
height: 40px;
background-color: #ef4444;
border-radius: 50%;
animation: blink 1.2s ease-in-out infinite;
}
@keyframes blink {
0% {
opacity: 1;
}
50% {
opacity: 0.2; /* 中間で薄くする */
}
100% {
opacity: 1;
}
}
動きすぎを避ける配慮(prefers-reduced-motion)
画面の動きが体調に影響する利用者もいるため、OS には「視差効果を減らす」といった設定があります。CSS では prefers-reduced-motion メディアクエリでその設定を検知でき、動きを抑える配慮ができます。次のように書くと、設定が有効な環境ではアニメーションをほぼ無効化できます。
/* 動きを減らす設定の利用者には、アニメーションをほぼ止める */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
}
}
ローディングスピナーのように動きが意味を持つものは完全には止めにくいですが、装飾目的の派手な動きは控えめにする、といった判断ができると親切です。すべてに対応する必要はありませんが、頭の片隅に置いておくとよいでしょう。
アニメーションが動かない・1 回で止まるときの原因
「@keyframes を書いたのに動かない」「一瞬動いてすぐ止まる」という相談はよくあります。原因はだいたい決まっているので、代表的なものを順に確認していきましょう。
@keyframes の名前と animation-name が一致していない
もっとも多いのが名前の打ち間違いです。@keyframes spin と定義したのに、要素側で animation: spinner 1s ... のように違う名前を書いていると、当てはまる定義が見つからず何も起きません。大文字・小文字も区別されるため、fadeIn と fadein は別物として扱われます。動かないときは、まず両者の名前が完全に一致しているかを見直してください。
iteration-count を指定せず 1 回で終わっている
「一瞬だけ動いて止まってしまう」場合、animation-iteration-count の指定漏れが疑われます。このプロパティの初期値は 1 なので、何も指定しないとアニメーションは 1 回だけ再生されて終わります。スピナーのように繰り返したいなら infinite、決まった回数なら 3 のように数値を指定する必要があります。一括指定なら animation: spin 1s linear infinite; のように infinite を忘れず加えましょう。
終了後に最初の状態へ戻ってしまう
1 回だけ再生するアニメーションで、「動き終わった瞬間にパッと元の見た目に戻る」ことがあります。これは animation-fill-mode の初期値が none で、再生前後は @keyframes の指定が無視されるためです。終了状態をそのまま保ちたいときは animation-fill-mode: forwards を指定します。たとえばフェードインで最後に opacity: 1 のまま表示を保ちたいなら、forwards が必要です。
duration を指定していない
animation-duration の初期値は 0s です。時間を指定し忘れると再生時間がゼロになり、アニメーションが目に見えないまま一瞬で終わってしまいます。一括指定で書くときは、最低でも animation-name と animation-duration の 2 つは必ず指定するようにしてください。
まとめ
@keyframes でアニメーションの動き(途中経過)を定義し、animation でそれを要素に適用するのが基本の流れです。@keyframes は from / to または 0%〜100% でキーフレームを書き、animation は animation: spin 1s linear infinite; のように名前・時間・繰り返しなどを 1 行でまとめて指定します。繰り返したいなら infinite、往復させたいなら alternate、終了状態を保ちたいなら forwards が鍵になります。transition がきっかけを必要とするのに対し、animation は自律的に・繰り返し動くのが大きな違いです。動かないときは名前の一致と iteration-count・duration の指定を見直し、利用者への配慮として prefers-reduced-motion も頭に入れておくと、より安心して使えます。