CSS キーフレーム アニメーション 9選 — 一行でコピペOK
CSS キーフレーム アニメーションは、@keyframesで定義した一連の変化をクラス名ひとつにまとめ、任意の要素へそのまま適用する書き方です。浮く・跳ねる・回る・脈打つ・揺れる・ぐらつく・点滅する・鼓動する・ぷるぷるする、9種類をtransformとopacityの2プロパティだけで組みました。
自動再生 · タイルを押すとその項目へ · すべて1つのzip
9つは名前の五十音順ではなく、要素が画面でどんな役割を担うかという順序で並べました。まず存在をそっと伝え(01・02)、次に進行中だと示し(03・04)、続けて注意を引き(05・06)、最後に状態が変わったことを知らせます(07〜09)。下の表で全体を見渡してから、各項目の下でコードを確認してください。
| クラス | 動くプロパティ | duration | 繰り返し |
|---|---|---|---|
.a-float |
translateY | 3秒 | 無限 |
.a-bounce |
translateY | 1秒 | 無限 |
.a-spin |
rotate | 1秒 | 無限 |
.a-pulse |
scale・opacity | 2秒 | 無限 |
.a-shake |
translateX | 0.5秒 | 1回(トリガー) |
.a-wobble |
translateX・rotate | 1秒 | 無限 |
.a-flash |
opacity | 1秒 | 無限 |
.a-heartbeat |
scale | 1.2秒 | 無限 |
.a-jelly |
scaleX・scaleY | 0.6秒 | 1回(トリガー) |
01ふわふわ浮く
.a-floatを付けると、translateYが0から-16pxのあいだをease-in-outでゆっくり往復するので、要素が宙に浮いているように見えます。イラストやアイコンバッジのように常に表示され続ける要素に使い、3秒という長めの周期で目に留まりすぎないようにしています。
$duration: 3s;
$easing: ease-in-out;
.a-float { animation: float $duration $easing infinite; }
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-16px); }
}
02跳ねる
.a-bounceはtranslateYを短く沈めてから大きく跳ね上げるcubic-bezierイージングで動き、床でボールが弾むような緩急を作ります。スクロール誘導の矢印や「もっと見る」アイコンのように、押せる場所だと動きで示したいときに向いています。
$duration: 1s;
$easing: cubic-bezier(.28, .84, .42, 1);
.a-bounce { animation: bounce $duration $easing infinite; }
@keyframes bounce {
0%, 100% { transform: translateY(0); }
30% { transform: translateY(-4px); }
55% { transform: translateY(10px); }
75% { transform: translateY(-2px); }
90% { transform: translateY(4px); }
}
03回転
.a-spinはrotateを0度から360度までlinearで回すだけの汎用クラスで、ローディングスピナーに限らず更新ボタンや同期アイコンにもそのまま付けられます。イージングを挟まず一定速度で回すので、一周ごとの継ぎ目が目立ちません。
$duration: 1s;
$easing: linear; // 一定速度なので継ぎ目が目立たない
.a-spin { animation: spin $duration $easing infinite; }
@keyframes spin {
to { transform: rotate(360deg); }
}
04パルス
.a-pulseはscaleを1から1.06のあいだ、opacityも1から0.92のあいだで同時に揺らし、レイアウトを崩さずに視線だけを引きます。ライブ状態のドットや強調したいCTAボタンのように、目立たせたいが主張しすぎたくない場面に向いています。
$duration: 2s;
$easing: ease-out;
.a-pulse { animation: pulse $duration $easing infinite; }
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.06); opacity: .92; }
}
05揺れる
.a-shakeはtranslateXを左右に6回短く往復させて0.5秒で自ら止まる単発クラスで、ループしません。フォームのエラー専用ではなく、新機能バッジを揺らす、チュートリアルで特定の箇所を指し示すといった汎用的な注意喚起に使います。
$duration: 500ms;
$easing: cubic-bezier(.36, .07, .19, .97);
$amount: 8px;
.a-shake.is-shaking { animation: shake $duration $easing 1; }
@keyframes shake {
10%, 90% { transform: translateX($amount * -.5); }
20%, 80% { transform: translateX($amount); }
30%, 70% { transform: translateX($amount * -1); }
40%, 60% { transform: translateX($amount * .7); }
50% { transform: translateX($amount * -.7); }
}
06ぐらぐら
.a-wobbleはtranslateXとrotateを同じ@keyframesの中でタイミングをずらして重ね、05の揺れより不規則で生きた動きになります。ドラッグできるカードや並べ替え中のリスト項目が「動かせます」と伝えたいときに向いています。
$duration: 1s;
$easing: ease-in-out;
.a-wobble { animation: wobble $duration $easing infinite; }
@keyframes wobble {
0%, 100% { transform: translateX(0) rotate(0deg); }
15% { transform: translateX(-6px) rotate(-4deg); }
30% { transform: translateX(5px) rotate(3deg); }
45% { transform: translateX(-4px) rotate(-2deg); }
60% { transform: translateX(3px) rotate(2deg); }
75% { transform: translateX(-2px) rotate(-1deg); }
}
07点滅
.a-flashはopacityを1→0→1の順で2回区切って点滅させ、要素を完全には消さずに確実に目を引きます。緊急の告知アイコンやライブ配信中を示すドットのように、はっきり点滅させたい場所で使います。
$duration: 1s;
$easing: ease-in-out;
.a-flash { animation: flash $duration $easing infinite; }
@keyframes flash {
0%, 50%, 100% { opacity: 1; }
25%, 75% { opacity: .15; }
}
08鼓動
.a-heartbeatはscaleを短く2回弾ませたあと休む「ドッ・ドッ・休み」のリズムで@keyframesを区切り、均等な繰り返しより実際の心拍に近い間隔で動きます。いいねアイコンの待機状態やダッシュボードの監視ドットに向いています。
$duration: 1.2s;
$easing: ease-in-out;
.a-heartbeat { animation: heartbeat $duration $easing infinite; }
@keyframes heartbeat {
0%, 100% { transform: scale(1); }
14% { transform: scale(1.24); }
28% { transform: scale(1); }
42% { transform: scale(1.18); }
70% { transform: scale(1); }
}
09ぷるぷる
.a-jellyはscaleXとscaleYを互いに逆方向へずらして動かし、押された瞬間にゼリーのように揺れてから元の形に戻ります。カート追加の完了アイコンやクリック後に弾むボタンのフィードバックに使います。
$duration: 600ms;
$easing: cubic-bezier(.68, -0.55, .27, 1.55);
.a-jelly.is-jellying { animation: jelly $duration $easing 1; }
@keyframes jelly {
0% { transform: scale(1, 1); }
20% { transform: scale(1.25, .75); }
40% { transform: scale(.75, 1.25); }
60% { transform: scale(1.15, .85); }
80% { transform: scale(.95, 1.05); }
100% { transform: scale(1, 1); }
}
CSSのanimationショートハンドはなぜこの順番なのか
animation:の1行に値をまとめて書くとき、順番を覚えていないとdurationとdelayを取り違えがちです。MDNのanimationショートハンドが示す順番は次のとおりです。
| 順番 | プロパティ | この記事の例 | 既定値 |
|---|---|---|---|
| 1 | animation-name | float | none |
| 2 | animation-duration | 3s | 0s |
| 3 | animation-timing-function | ease-in-out | ease |
| 4 | animation-delay | (省略時0) | 0s |
| 5 | animation-iteration-count | infinite | 1 |
| 6 | animation-direction | (省略時normal) | normal |
| 7 | animation-fill-mode | (省略時none) | none |
| 8 | animation-play-state | (省略時running) | running |
animation-fill-modeは、再生の前後にキーフレームの値を保つかどうかを決めます。MDNのanimation-fill-modeがまとめる4つの値は次のとおりです。
| 値 | 効果 |
|---|---|
none |
既定。再生前後とも元のスタイルのまま |
forwards |
再生が終わったあと、最後のキーフレームの値を保つ |
backwards |
animation-delayのあいだ、最初のキーフレームの値を先に適用する |
both |
forwardsとbackwardsの両方を適用する |
05の揺れと09のぷるぷるは1回で再生を終えますが、どちらも0%と100%が元の形と同じなので、fill-modeを指定しなくても自然に定位置へ戻ります。最後のキーフレームが元の見た目と違う場合は、forwardsを足さないと再生後の形が戻ってしまいます。
どこで壊れるか — 落とし穴
.a-floatと.a-spinのように別々のユーティリティクラスを同じ要素へ重ねて付けると、どちらもanimationショートハンドを丸ごと宣言しているため、CSSのカスケード規則で後に書かれたクラスが先のクラスを完全に上書きし、片方しか動きません。ひとつの要素へ2つの動きを重ねるには、animation-name: float, spin;のように名前をカンマで並べ、animation-durationやanimation-timing-functionも同じ数だけ並べる必要があります。両方が同じプロパティ(transform)を動かす場合は既定のreplaceが互いを上書きするため、animation-composition: addを指定すると2つのtransform値を足し合わせて両方を反映できます。
05の揺れと09のぷるぷるは一度きりで止まる単発アニメーションで、24フレームの計測のうちそれぞれ6フレーム・10フレームしか動きが記録されませんでした。この2つは9つの中でも可動時間が際立って短いのに、変化の強度の平均値はぷるぷるの方が9つの中でいちばん高く出ています。短い動きほど地味になるとは限らないと、実際に組んで測るまでわかりませんでした。この違いも含めた9ファイルの解凍パスワードはnft4q6u2で、下のカードから受け取れます。
アクセシビリティ
動作を減らす設定を有効にしているユーザーのために、9つとも@media (prefers-reduced-motion: reduce)の中でanimation: noneに切り替えます。動きだけを止めて情報は残すのが原則で、9つとも静止した状態のまま内容自体は変わらず読めます。このメディアクエリをどのブラウザが読み取るかは、MDNのprefers-reduced-motionのドキュメントにある対応表で確認できます。
@media (prefers-reduced-motion: reduce) {
.a-float, .a-bounce, .a-spin, .a-pulse,
.a-wobble, .a-flash, .a-heartbeat { animation: none; }
.a-shake.is-shaking, .a-jelly.is-jellying { animation: none; }
}
ほかのCSSアニメーションの記事はCSSアニメーションのカテゴリーに、このサイトの成り立ちは紹介ページにまとめています。
FAQ
9つを1つの画面に全部使ってもいいですか?
入れること自体はできますが、1画面には2〜3個までにとどめるのがおすすめです。04のパルスと07の点滅のように、どちらも「注目して」という信号を持つクラスを同じ画面に何個も置くと、どれも特別に見えなくなります。03の回転と08の鼓動のように状態が異なるもの同士なら、並んでいても違和感はありません。
2つのアニメーションを1つの要素に重ねるにはどうすればいいですか?
クラスを2つそのまま付けるだけでは、後に書いたクラスのanimationがすべてを上書きしてしまい片方しか動きません。animation-nameにカンマ区切りで名前を並べ、同じプロパティを動かす場合はanimation-composition: addやaccumulateで合成する方法を使ってください。具体的な例は落とし穴のセクションにあります。
Reactではクラスの切り替えをどう扱いますか?
zipのreact/フォルダの各コンポーネントは、duration・easing・colorをpropsで受け取ってCSS変数へ渡す作りです。05の揺れと09のぷるぷるのように一度きりで止まるものは、useStateでクラスをオンにし、onAnimationEndでオフに戻すことで、もう一度クリックしたときにも再生されるようにしています。