CSSアニメーションとは、CSSだけで要素の見た目を時間とともに変化させる仕組みで、「状態が変わったときに補間する transition」と「@keyframes で定義した動きを再生する animation」の2種類があります。

この記事では、どちらを使うかの判断表、全プロパティの一覧、実用パーツ20個のコピペ用コード をまとめました。すべて Playwright 1.56 + Chromium 141 で動かし、getAnimations() と getComputedStyle() で 途中の値まで実測 しました(数値で示します)。新機能の対応状況(2026年9月時点)、transform でレイアウト計算が1秒61回→1回に減った実測、prefers-reduced-motion の落とし穴も扱います。


CSSアニメーションの早見表

やりたいこと 仕組み 書き方の核 Chromium 141 実測
ホバーで拡大・色変化 transition transition: transform .2s; 100ms後 scale(1.0353) → 完了 1.05
表示時にフェードイン animation animation: fade-in .6s ease-out both; 250ms後 opacity 0.654
回り続けるスピナー animation animation: spin 1s linear infinite; iterations Infinity
点滅 animation + steps() steps(1, end) infinite opacity は 1 と 0 のみ
height: auto で開閉 transition interpolate-size: allow-keywords; 100ms後 48.39px → 84px
display: none からフェード transition @starting-style + allow-discrete 100ms後 opacity 0.576
モーダルの開閉 transition display / overlay に allow-discrete 閉じて100ms後も display: block
スクロール量に連動 animation animation-timeline: scroll(root); scrollY 1000px で 26.178%
画面に入ったら表示 animation animation-timeline: view(); 画面外 0 → 画面内 1
画面切り替えをなめらかに View Transitions document.startViewTransition(fn) 250ms のクロスフェードが自動で付く

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「CSS アニメーション|transitionとkeyframesの違いと実用20選」


transition と animation の違いと選び方

「ホバーやクラス付与で A→B を補間するなら transition、途中の段階・くり返し・自動再生が必要なら animation」 が判断の基本です。

比べる点 transition animation(@keyframes)
動き出すきっかけ 値の変化(:hover、クラス付与) 適用された瞬間に自動再生
途中の段階 開始と終了の2点 0%〜100% の任意の点
くり返し・一時停止 できない infinite / paused
初回表示で動かす @starting-style が必要 そのまま動く

transition のプロパティ一覧

プロパティ 役割 例 初期値
transition-property 補間するプロパティ transform, opacity all
transition-duration かける時間 .3s 0s
transition-timing-function 速度の変化 ease-out ease
transition-delay 開始までの待ち時間 .1s 0s
transition-behavior display など離散値も扱う allow-discrete normal
/* プロパティ 時間 イージング 遅延(カンマで複数) */
.btn { transition: background-color .3s ease, transform .2s ease-out; }
.btn:hover { background-color: #1a73e8; transform: scale(1.05); }
  • 時間の値が2つあれば1つ目が duration、2つ目が delay です。
  • transition は通常の状態(.btn)に書く と、ホバーを外したときも逆向きに動きます。

animation(@keyframes)のプロパティ一覧

プロパティ 役割 よく使う値 初期値
animation-name @keyframes 名 fade-in none
animation-duration 1回の時間 .6s 0s
animation-timing-function 速度の変化 ease-out / linear / steps(4) ease
animation-delay 待ち時間(負の値で途中から) .3s / -.5s 0s
animation-iteration-count 回数 3 / infinite 1
animation-direction 向き reverse / alternate normal
animation-fill-mode 前後の値を保つか forwards / both none
animation-play-state 再生・停止 paused running
animation-timeline 進める基準 scroll() / view() auto
@keyframes slide-up {
  from { opacity: 0; transform: translateY(24px); }
  to   { opacity: 1; transform: none; }
}
/* 名前 時間 イージング 遅延 回数 向き fill-mode */
.item { animation: slide-up .6s ease-out .3s 1 normal both; }

animation-fill-mode の違い(実測)

省略(none)すると、終わった瞬間に元のスタイルへ戻ります。

指定 遅延中 終了後 実測
none 元のスタイル 元に戻る 元の opacity 0.2 に戻った
forwards 元のスタイル 最後の値を保持 1 のまま
backwards 最初の値 元に戻る —
both 最初の値 最後の値を保持 遅延0.3sの要素が250ms時点で 0、1.25s時点で 1

イージング:cubic-bezier() と steps()

値 動き 向いている用途
linear 一定 スピナー、スクロール連動
ease ゆっくり→速く→ゆっくり 汎用
ease-in / ease-out ゆっくり始まる / ゆっくり終わる 退場 / 登場・ホバー
cubic-bezier(x1,y1,x2,y2) 自由な曲線 行き過ぎて戻る動き
steps(n, end) n段階でカクッと進む 点滅、タイピング風

cubic-bezier() は y を 1 より大きくすると「少し行き過ぎて戻る」動き になります(例:cubic-bezier(.34, 1.56, .64, 1))。

steps() は途中を補間しません。 幅 0→12ch・2秒・steps(12, end) は250ms(1.5段階目)で幅 8px=1段階分でした。


実用パーツ20選(コピペ用)

Chromium 141 で動作を確認したコードです。

表示時に動かす(1〜5)

/* 1. フェードイン */
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
.fade-in { animation: fade-in .6s ease-out both; }

/* 2. 下からスライドイン(@keyframes slide-up は上の一覧のもの) */
.slide-up { animation: slide-up .6s ease-out both; }

/* 3. 順番に表示 */
.list > li { animation: slide-up .5s ease-out both; }
.list > li:nth-child(2) { animation-delay: .1s; }
.list > li:nth-child(3) { animation-delay: .2s; }

/* 4. タイピング風 */
@keyframes typing { from { width: 0; } }
.typing { overflow: hidden; white-space: nowrap; width: 12ch;
  animation: typing 2s steps(12, end); }

/* 5. 追加した要素(通知)をフェードイン */
.toast { opacity: 1; transition: opacity .3s; }
@starting-style { .toast { opacity: 0; } }

5は追加直後 @starting-style の値から補間が始まり、100ms後 opacity 0.468 でした。

ホバー・押下(6〜10)

/* 6. ホバーで拡大+色 */
.btn { transition: background-color .3s, transform .2s; }
.btn:hover { background-color: #1a73e8; transform: scale(1.05); }

/* 7. 押した感触 */
.btn:active { transform: scale(.96); }

/* 8. カードを浮かせる(行き過ぎて戻る) */
.card { transition: transform .3s cubic-bezier(.34, 1.56, .64, 1), box-shadow .3s; }
.card:hover { transform: translateY(-4px); box-shadow: 0 8px 24px rgb(0 0 0 / .15); }

/* 9. 左から伸びる下線 */
.underline { position: relative; }
.underline::after { content: ""; position: absolute; left: 0; bottom: 0;
  width: 100%; height: 2px; background: currentColor;
  transform: scaleX(0); transform-origin: left; transition: transform .3s; }
.underline:hover::after { transform: scaleX(1); }

/* 10. 入力エラーで揺らす */
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20%, 60% { transform: translateX(-6px); }
  40%, 80% { transform: translateX(6px); }
}
.shake { animation: shake .4s; }

10の再実行はクラスを外して void el.offsetWidth; のあと付け直します。

くり返す動き(11〜14)

/* 11. スピナー */
@keyframes spin { to { transform: rotate(360deg); } }
.spinner { width: 32px; height: 32px; border: 4px solid #ddd;
  border-top-color: #333; border-radius: 50%;
  animation: spin 1s linear infinite; }

/* 12. 点滅(中間の値を作らない) */
@keyframes blink { 50% { opacity: 0; } }
.blink { animation: blink 1s steps(1, end) infinite; }

/* 13. 鼓動 */
@keyframes pulse { 50% { transform: scale(1.08); } }
.pulse { animation: pulse 1.5s ease-in-out infinite; }

/* 14. 一時停止 */
.spinner.is-paused { animation-play-state: paused; }

14の実測:1.25秒後、running のスピナーは currentTime が 1283ms、paused は 0 のままでした。

開閉・モーダル(15〜18)

/* 15. display: none からフェードイン */
.panel { display: none; opacity: 0;
  transition: opacity .3s, display .3s allow-discrete; }
.panel.is-open { display: block; opacity: 1; }
@starting-style { .panel.is-open { opacity: 0; } }

/* 16. アコーディオン(height: auto へ補間) */
:root { interpolate-size: allow-keywords; }
.acc { height: 0; overflow: hidden; transition: height .3s; }
.acc.is-open { height: auto; }   /* または height: calc-size(auto, size) */

/* 17. <details> をなめらかに開く */
details::details-content { height: 0; overflow: clip;
  transition: height .3s, content-visibility .3s allow-discrete; }
details[open]::details-content { height: auto; }

/* 18. モーダル(<dialog>) */
dialog { opacity: 0; transform: translateY(16px);
  transition: opacity .3s, transform .3s,
    display .3s allow-discrete, overlay .3s allow-discrete; }
dialog[open] { opacity: 1; transform: none; }
@starting-style { dialog[open] { opacity: 0; transform: translateY(16px); } }
dialog::backdrop { background: rgb(0 0 0 / .4); }
openBtn.addEventListener('click', () => dialog.showModal());
closeBtn.addEventListener('click', () => dialog.close());
  • 17は開いて60ms後 58.94px、完了後 106px でした。
  • 18は display と overlay に allow-discrete を付けると、閉じる動きが終わるまで表示とトップレイヤーが保たれます。close() から100ms後も display: block(opacity 0.531)、500ms後に none でした。

スクロール・画面切り替え(19〜20)

/* 19-a. 読了プログレスバー */
@keyframes grow { from { transform: scaleX(0); } }
.progress { position: fixed; top: 0; left: 0; width: 100%; height: 4px;
  background: #1a73e8; transform-origin: left;
  animation: grow linear both;
  animation-timeline: scroll(root);  /* ショートハンドより後に書く */ }

/* 19-b. 画面に入ったら表示 */
@keyframes reveal { from { opacity: 0; transform: translateY(40px); } }
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .reveal { animation: reveal linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 30%; }
  }
}

animation-timeline はショートハンドで初期値に戻るため、後に書きます。 スクロール位置で進むので duration は不要です。

/* 20. View Transitions(同じページ内の切り替え) */
const update = () => { title.textContent = '更新しました'; };
document.startViewTransition ? document.startViewTransition(update) : update();
/* 時間を変える/特定の要素を別に動かす */
::view-transition-old(root), ::view-transition-new(root) { animation-duration: .4s; }
.hero { view-transition-name: hero; }
/* ページをまたぐ遷移は両方のページに */
@view-transition { navigation: auto; }

Chromium 141 で動いているか確認した結果

環境:Playwright 1.56 + Chromium 141.0.7390.37(ヘッドレス、1280×800)。

読み込み直後の getAnimations()

要素 名前 状態 回数 時間/遅延(ms) fill タイムライン
.fade-in fade-in running 1 600 / 0 both DocumentTimeline
遅延つき .slide-up slide-up running 1 600 / 300 both DocumentTimeline
.spinner spin running Infinity 1000 / 0 none DocumentTimeline
.is-paused spin paused Infinity 1000 / 0 none DocumentTimeline
.progress grow running 1 (スクロール) both ScrollTimeline
.reveal reveal running 1 (スクロール) both ViewTimeline

スクロール連動と View Transitions

scrollY プログレスバー scaleX currentTime .reveal opacity
0 0 0% 0
1000 0.26178 26.178% 0
2200 0.575916 57.5916% 1
3820(最下部) 1 100% 1

スクロール連動の currentTime はパーセントで返りました。 startViewTransition() では ::view-transition-old(root) に -ua-view-transition-fade-out、new に fade-in などが 250ms で自動的に付きました。


よくあるつまずきと直し方(直す前と後の実測)

つまずき 直す前(実測) 直し方 直した後(実測)
height: auto の開閉が一瞬 100ms後すでに 84px、transition なし :root { interpolate-size: allow-keywords; } か calc-size(auto, size) 100ms後 48.39px → 84px
display: none から表示でフェードしない 100ms後 opacity 1、transition なし display .3s allow-discrete + @starting-style 100ms後 0.576
終わると元に戻る 終了後 opacity 0.2 animation-fill-mode: forwards 1 を保持
スクロール連動が効かない animation-timeline がショートハンドで上書き ショートハンドの 後 に書く ScrollTimeline で進行

display: none の要素には変化前のスタイルがなく、@starting-style がその開始値を与えます。 どの直し方も、未対応ブラウザでは「一瞬で切り替わる」だけなので段階的に導入できます。


パフォーマンス:transform と opacity を選ぶ理由

ブラウザの描画は「スタイル計算 → レイアウト → ペイント → 合成」の順です。 left や width を動かすと毎フレームレイアウトからやり直しますが、transform と opacity は多くの場合、合成の段階だけで処理 できます。

Performance.getMetrics(DevTools Protocol)で1秒間の回数を比べました。

動かしたプロパティ レイアウト回数/秒 スタイル再計算/秒
left: 0 → 100px 61回 61回
transform: translateX(0 → 100px) 1回 1回

位置は translate()、大きさは scale()、表示は opacity で動かす と合成で済みます。

will-change: transform; は事前にレイヤーを用意させる指定 です。メモリを使うため、カクつきが確認できた要素だけに付けるのが基本です。


prefers-reduced-motion への対応

OSの「動きを減らす」設定は prefers-reduced-motion: reduce で検出できます。

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

emulateMedia({ reducedMotion: 'reduce' }) で確認した結果です。

項目 animation-delay 行なし 行あり
スピナーの時間 / 回数 1e-05s / 1(即終了) 同じ
遅延0.3sの要素(50ms後) running(まだ待機中) finished
スクロール連動(50ms後) running running

時間を縮めるだけでは遅延とスクロール連動が残ります。 遅延は animation-delay: 0s、スクロール連動は19-bのように no-preference の中でだけ付ける と確実です。


新しい機能の対応状況(2026年9月時点)

機能 状況 出典・補足
transition / animation 主要ブラウザすべて —
@starting-style、allow-discrete Baseline(2024年8月〜) MDN
View Transitions(同一ドキュメント) Baseline(2025年10月〜) Firefox 144 で3エンジン対応(web.dev)
animation-timeline(scroll() / view()) 一部のみ Chrome 115以降・Safari 26。MDN は Limited availability
interpolate-size / calc-size() Chromium系のみ Chrome 129以降(web-features)

Chromium 141 では CSS.supports() などで上の機能すべてが true でした。「最初は非表示」のパーツだけ @supports で分岐 すると、未対応ブラウザでも内容が見えます。


よくある質問

Q. transition と animation はどちらを使えばよいですか? A. ホバーやクラスの付け外しで A→B を切り替えるなら transition、くり返し・途中の段階・自動再生が必要なら animation です。「くり返すか」で判断すると決めやすくなります。

Q. アニメーションがカクつきます。 A. 動かすプロパティを transform と opacity に置き換え、それでも必要な要素だけに will-change を付けます。


まとめ

  • transition は「状態変化の2点を補間」、animation は「@keyframes を自動再生・くり返し」
  • 時間が2つなら「duration → delay」の順。animation-timeline はショートハンドの後に書く
  • height: auto は interpolate-size: allow-keywords、display: none からは @starting-style + allow-discrete
  • 動かすのは transform と opacity。実測でレイアウトが61回→1回/秒に
  • スクロール連動と View Transitions はJSなしで使える。対応状況を見て段階的に
  • prefers-reduced-motion は delay とスクロール連動まで含めて対応 する

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「CSS アニメーション|transitionとkeyframesの違いと実用20選」

株式会社RJCでは、画面の心地よい動きとパフォーマンスの両立を、実際に計測しながらプロジェクトの中で仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?