GODRICH

【JS最小・CSSだけ】css ページ遷移 アニメーション9選

css ページ遷移 アニメーションは、2枚の画面要素を opacity や transform、clip-path だけで重ねて、ライブラリなしにページ間の切り替えを表現する手法です。この記事では9種類を選び、グリッドではそれぞれ2秒ごとに前後の画面が自動で入れ替わります。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

並び順は実装の難しさではなく、実際のサイトでよく使う場面の順に並べています。最初の3つは記事や商品の一般的な切り替え(01〜03)、次の3つはクリックした場所やカードを起点にした切り替え(04〜06)、最後の3つはタブや並び替え、テーマ切替のような UI 内の細かい遷移です(07〜09)。HTML と CSS だけで完結させるため、JavaScript のファイルは zip に1つも入っていません。項目ごとに要点となるコードだけを載せ、完全なファイルは zip の中です。気に入ったものがあれば zip をダウンロードして、自分のページで試してください。

01既定のクロスフェード

前のページが薄れて消え、次のページがその場に重なって現れます。opacity だけを動かすので、レイアウトの再計算が起きません。ブログ記事の遷移や一般的なページ移動に向いています。

300ms느리게 500ms살짝 확대
.page--a { animation: cross-a $dur-loop $easing infinite; }
.page--b { animation: cross-b $dur-loop $easing infinite; opacity: 0; }

@keyframes cross-a {
  0%, 5% { opacity: 1; }
  50% { opacity: 0; }
  95%, 100% { opacity: 1; }
}
@keyframes cross-b {
  0%, 5% { opacity: 0; }
  50% { opacity: 1; }
  95%, 100% { opacity: 0; }
}

02左右スライド

次のページが右から押し入り、前のページは左へ押し出されます。2枚とも同じ transform: translateX を逆方向に動かすだけで、方向を反転すれば「戻る」動作にもそのまま使えます。ステップ形式のフォームやモバイルの戻る・進むに向いています。

방향 반전250ms그림자 추가
.page--a { animation: slide-a $dur-loop $easing infinite; }
.page--b { animation: slide-b $dur-loop $easing infinite; transform: translateX(100%); }

@keyframes slide-a {
  0%, 5% { transform: translateX(0); }
  50% { transform: translateX(-100%); }
  95%, 100% { transform: translateX(0); }
}
@keyframes slide-b {
  0%, 5% { transform: translateX(100%); }
  50% { transform: translateX(0); }
  95%, 100% { transform: translateX(100%); }
}

03サムネイル→ヒーロー要素の共有

一覧のサムネイルがそのまま伸びて、次ページの大きな画像へつながって見えます。デモでは同じ要素を width・height・top で動かしていますが、実際のページ遷移では View Transitions API の view-transition-name を使うと、別々の文書間でもブラウザが自動で補間してくれます。商品詳細やポートフォリオ詳細への遷移に向いています。

view-transition-name400ms라운드 유지
.hero {
  position: absolute; left: clamp(12px, 5vw, 18px); top: 46px; width: 30%; height: 44px;
  transform-origin: top left;
  animation: hero-grow $dur-loop $easing infinite;
}

@keyframes hero-grow {
  0%, 40% { width: 30%; height: 44px; }
  70%, 90% { width: 88%; height: 108px; top: 12px; }
  100% { width: 30%; height: 44px; top: 46px; }
}

04クリック地点から円形に広がる

クリックした地点から円が広がり、次ページを覆います。width や left ではなく clip-path の circle() だけを動かすので、コンポジター側の処理で済み、レイアウトの再計算が起きません。ダークモード切替やテーマ切替ボタンに向いています。

clip-path circle600ms중앙 고정
.page--circle {
  clip-path: circle(0% at 88% 14%);
  animation: circle-grow $dur-loop $easing infinite;
}

@keyframes circle-grow {
  0%, 5% { clip-path: circle(0% at 88% 14%); }
  50% { clip-path: circle(150% at 88% 14%); }
  95%, 100% { clip-path: circle(0% at 88% 14%); }
}

05アプリ風スタックプッシュ

新しい画面が右から積み重なるように入り、前の画面はわずかに縮んで暗くなります。3つの要素(前面・背面・暗幕)を同時に動かしても、それぞれ独立した animation なので仕組みは崩れません。モバイルWebアプリや設定画面の遷移に向いています。

280ms뒤 화면 어둡게엣지 스와이프
.page--a { animation: push-back $dur-loop $easing infinite; transform-origin: left center; }
.page--b { animation: push-front $dur-loop $easing infinite; transform: translateX(100%); }
.dim { animation: push-dim $dur-loop $easing infinite; }

@keyframes push-back {
  0%, 5% { transform: scale(1); }
  50% { transform: scale(.94); }
  95%, 100% { transform: scale(1); }
}
@keyframes push-dim {
  0%, 5% { opacity: 0; }
  50% { opacity: .18; }
  95%, 100% { opacity: 0; }
}

06カード→詳細へズーム

クリックしたカードが画面いっぱいに拡大して詳細ページになります。cell--focus を scale(4) まで広げてから detail を重ねて表示するので、拡大の最中に文字が歪んでも、最終状態では別の要素に差し替わって見えます。ギャラリーやニュースカード一覧に向いています。

350ms모서리 반경 0으로배경 어둡게
.cell--focus { animation: zoom-card $dur-loop $easing infinite; }
.detail {
  position: absolute; inset: 0; opacity: 0;
  animation: zoom-detail $dur-loop $easing infinite;
}

@keyframes zoom-card {
  0%, 40% { transform: scale(1); }
  70% { transform: scale(4); }
  100% { transform: scale(1); }
}
@keyframes zoom-detail {
  0%, 55% { opacity: 0; }
  75%, 90% { opacity: 1; }
  100% { opacity: 0; }
}

07タブコンテンツのクロスフェード

タブを切り替えると前のコンテンツが消え、新しいコンテンツが8pxずれて重なって現れます。01の単純なクロスフェードと違い、opacity と transform: translateY を同時に動かすことで、切り替えに向きの手がかりを加えています。設定タブやダッシュボードのタブコンテンツに向いています。

200ms8px 오프셋깜빡임 없이
.page--a { animation: tab-a $dur-loop $easing infinite; }
.page--b { animation: tab-b $dur-loop $easing infinite; opacity: 0; transform: translateY(8px); }

@keyframes tab-a {
  0%, 5% { opacity: 1; transform: translateY(0); }
  50% { opacity: 0; transform: translateY(-8px); }
  95%, 100% { opacity: 1; transform: translateY(0); }
}
@keyframes tab-b {
  0%, 5% { opacity: 0; transform: translateY(8px); }
  50% { opacity: 1; transform: translateY(0); }
  95%, 100% { opacity: 0; transform: translateY(8px); }
}

08並び替え時のアイテム移動

並び替えを変えると、1位と2位の項目が新しい位置へ滑らかに入れ替わります。FLIP 技法の考え方どおり、実際に動くのは34pxという最終位置の差分だけで、要素の再配置そのものは CSS に任せています。価格の並び替えやランキング一覧に向いています。

FLIP 기법300ms순위 뱃지 강조
.row--1 { animation: swap-down $dur-loop $easing infinite; }
.row--2 { animation: swap-up $dur-loop $easing infinite; }

@keyframes swap-down {
  0%, 5% { transform: translateY(0); }
  50% { transform: translateY(34px); }
  95%, 100% { transform: translateY(0); }
}
@keyframes swap-up {
  0%, 5% { transform: translateY(0); }
  50% { transform: translateY(-34px); }
  95%, 100% { transform: translateY(0); }
}

09ダークモードへのワイプ切替

画面の角から暗い色が斜めに広がり、ダークモードへ切り替わります。clip-path の polygon() で三角形を0から画面の外まで広げるだけなので、切り替える色の面積が大きくても計算量は変わりません。ダークモード切替ボタンや設定画面に向いています。

대각선450ms가장자리부터
.page--wipe {
  clip-path: polygon(0 0, 0 0, 0 0);
  animation: dark-wipe $dur-loop $easing infinite;
}

@keyframes dark-wipe {
  0%, 35% { clip-path: polygon(0 0, 0 0, 0 0); }
  75%, 90% { clip-path: polygon(0 0, 200% 0, 0 200%); }
  100% { clip-path: polygon(0 0, 0 0, 0 0); }
}

どこで崩れるか — 落とし穴

9つとも transform・opacity・clip-path のどれかだけで動く理由は、この3つがブラウザのコンポジター側で処理され、レイアウトの再計算(リフロー)を起こさないからです。MDN の CSS transitions ガイド にあるとおり、width や top、left をアニメーションさせると毎フレームでレイアウトが計算し直され、スクロールやタップの反応が遅れて見えます。02のスライドと05のスタックプッシュで最初につまずいたのがここで、transform: translateX の代わりに left の数値を動かしていた版では、480pxの舞台でもコマ落ちが出ていました。

もう一つの落とし穴は06のズームカードです。scale(4) まで拡大する間、拡大された要素の内側にある文字がぼやけて見える瞬間があります。ここは動きを隠すためではなく、detail という別要素を透明度だけで重ねて、拡大が終わるタイミングで文字を差し替えることで解決しました。修正済みの9ファイルを開くには amqzkrw2 という文字列が必要です。下のカードから zip を取ってください。

3つ目はブラウザ間の違いです。03のような遷移を同一ドキュメント内で View Transitions API として実装する場合、対応状況は環境ごとに差があります。caniuse の View Transitions API によると、同一ドキュメント内の遷移は Chrome 111 以降・Safari 18 以降・Firefox 144 以降で使えますが、それより前のブラウザでは何も起きないため、この記事のデモのように transform・opacity・clip-path だけで組んだ版を常に用意しておくと安全です。

アクセシビリティ

9つとも prefers-reduced-motion: reduceanimation: none になり、動きが止まります。04の円形拡散と09のワイプは、動きを止めると同時に最終状態の clip-path をそのまま指定してあるので、次のページが完全に隠れたまま止まることはありません。08の並び替えだけは、動きを止めても要素の最終位置は変わらないため、順位の入れ替わり自体は瞬時に反映されます。

@media (prefers-reduced-motion: reduce) {
  .page--a, .page--b { animation: none !important; }
  .page--circle { animation: none !important; clip-path: circle(0% at 88% 14%); }
}

ページ遷移以外の CSS アニメーションは CSS アニメーションのカテゴリ に、このサイトについては 紹介ページ にまとめています。

FAQ

css ページ遷移 アニメーションは JavaScript なしで作れますか

はい、作れます。この記事の9つはすべて CSS の animation@keyframes だけで実装していて、JavaScript は0行です。ただし実際のサイトで別々の URL 間を遷移させる場合は、どのタイミングで次のページの HTML を差し込むかを制御するために、最小限の JavaScript か View Transitions API のような仕組みが必要になることがあります。

ページ遷移 アニメーション css のみで実装するときに一番崩れやすいのはどれですか

06のカード→詳細へのズームです。scale(4) のような大きな拡大率を使うと、拡大された要素の内側の文字や画像が一瞬ぼやけて見えます。この記事のデモでは、拡大が終わる直前に detail という別要素を opacity だけで重ねることで、文字がぼやけたまま止まって見える状態を避けています。

View Transitions API と transform だけの実装はどちらを使うべきですか

同じ文書内のタブや一覧の切り替えなら、この記事のように transform・opacity・clip-path だけで組む方が、ブラウザを選ばず動くので安全です。異なる URL 間のページ遷移で03のような共有要素の動きを狙うなら、対応ブラウザに限って View Transitions API を使い、非対応ブラウザ向けにはこの記事のデモと同じ transform ベースの代替を用意するのが確実です。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。